Aegisub/aegisub/src/audio_timing_karaoke.cpp
Thomas Goyne aaf7285a6e Remove UI-related responsibilities from AudioController
Keeping track of audio markers and labels to be shown in the audio
display is not particularly related to AudioController's primary
responsiblity of controlling the playback of audio.

Originally committed to SVN as r6427.
2012-02-01 23:59:12 +00:00

345 lines
11 KiB
C++

// Copyright (c) 2011, Thomas Goyne <plorkyeran@aegisub.org>
//
// 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.
//
// 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.
//
// Aegisub Project http://www.aegisub.org/
//
// $Id$
/// @file audio_timing_karaoke.cpp
/// @brief Timing mode for karaoke
/// @ingroup audio_ui
///
#include "config.h"
#include <libaegisub/signal.h>
#include "ass_dialogue.h"
#include "ass_file.h"
#include "ass_karaoke.h"
#include "audio_controller.h"
#include "audio_renderer.h"
#include "audio_timing.h"
#include "include/aegisub/context.h"
#include "main.h"
#include "pen.h"
#include "utils.h"
#ifndef AGI_PRE
#include <deque>
#endif
/// @class KaraokeMarker
/// @brief AudioMarker implementation for AudioTimingControllerKaraoke
class KaraokeMarker : public AudioMarker {
int position;
Pen *pen;
FeetStyle style;
public:
int GetPosition() const { return position; }
wxPen GetStyle() const { return *pen; }
FeetStyle GetFeet() const { return style; }
bool CanSnap() const { return false; }
void Move(int new_pos) { position = new_pos; }
KaraokeMarker(int position, Pen *pen, FeetStyle style)
: position(position)
, pen(pen)
, style(style)
{
}
KaraokeMarker(int position) : position(position) { }
operator int() const { return position; }
};
/// @class AudioTimingControllerKaraoke
/// @brief Karaoke timing mode for timing subtitles
///
/// Displays the active line with draggable markers between each pair of
/// adjacent syllables, along with the text of each syllable.
///
/// This does not support \kt, as it inherently requires that the end time of
/// one syllable be the same as the start time of the next one.
class AudioTimingControllerKaraoke : public AudioTimingController {
std::deque<agi::signal::Connection> slots;
agi::signal::Connection& file_changed_slot;
agi::Context *c; ///< Project context
AssDialogue *active_line; ///< Currently active line
AssKaraoke *kara; ///< Parsed karaoke model provided by karaoke controller
size_t cur_syl; ///< Index of currently selected syllable in the line
/// Pen used for the mid-syllable markers
Pen separator_pen;
/// Pen used for the start-of-line marker
Pen start_pen;
/// Pen used for the end-of-line marker
Pen end_pen;
/// Immobile marker for the beginning of the line
KaraokeMarker start_marker;
/// Immobile marker for the end of the line
KaraokeMarker end_marker;
/// Mobile markers between each pair of syllables
std::vector<KaraokeMarker> markers;
/// Marker provider for video keyframes
AudioMarkerProviderKeyframes keyframes_provider;
/// Marker provider for video playback position
VideoPositionMarkerProvider video_position_provider;
/// Labels containing the stripped text of each syllable
std::vector<AudioLabel> labels;
bool auto_commit; ///< Should changes be automatically commited?
bool auto_next; ///< Should user-initiated commits automatically go to the next?
int commit_id; ///< Last commit id used for an autocommit
void OnAutoCommitChange(agi::OptionValue const& opt);
void OnAutoNextChange(agi::OptionValue const& opt);
void DoCommit();
public:
// AudioTimingController implementation
void GetMarkers(const TimeRange &range, AudioMarkerVector &out_markers) const;
wxString GetWarningMessage() const { return ""; }
TimeRange GetIdealVisibleTimeRange() const;
void GetRenderingStyles(AudioRenderingStyleRanges &ranges) const;
TimeRange GetPrimaryPlaybackRange() const;
void GetLabels(const TimeRange &range, std::vector<AudioLabel> &out_labels) const;
void Next();
void Prev();
void Commit();
void Revert();
bool IsNearbyMarker(int ms, int sensitivity) const;
AudioMarker * OnLeftClick(int ms, int sensitivity, int);
AudioMarker * OnRightClick(int, int, int) { return 0; }
void OnMarkerDrag(AudioMarker *marker, int new_position, int);
AudioTimingControllerKaraoke(agi::Context *c, AssKaraoke *kara, agi::signal::Connection& file_changed);
};
AudioTimingController *CreateKaraokeTimingController(agi::Context *c, AssKaraoke *kara, agi::signal::Connection& file_changed)
{
return new AudioTimingControllerKaraoke(c, kara, file_changed);
}
AudioTimingControllerKaraoke::AudioTimingControllerKaraoke(agi::Context *c, AssKaraoke *kara, agi::signal::Connection& file_changed)
: file_changed_slot(file_changed)
, c(c)
, active_line(c->selectionController->GetActiveLine())
, kara(kara)
, cur_syl(0)
, separator_pen("Colour/Audio Display/Syllable Boundaries", "Audio/Line Boundaries Thickness", wxPENSTYLE_DOT)
, start_pen("Colour/Audio Display/Line boundary Start", "Audio/Line Boundaries Thickness")
, end_pen("Colour/Audio Display/Line boundary End", "Audio/Line Boundaries Thickness")
, start_marker(active_line->Start, &start_pen, AudioMarker::Feet_Right)
, end_marker(active_line->End, &end_pen, AudioMarker::Feet_Left)
, keyframes_provider(c, "Audio/Display/Draw/Keyframes in Karaoke Mode")
, video_position_provider(c)
, auto_commit(OPT_GET("Audio/Auto/Commit")->GetBool())
, auto_next(OPT_GET("Audio/Next Line on Commit")->GetBool())
, commit_id(-1)
{
slots.push_back(kara->AddSyllablesChangedListener(&AudioTimingControllerKaraoke::Revert, this));
slots.push_back(OPT_SUB("Audio/Auto/Commit", &AudioTimingControllerKaraoke::OnAutoCommitChange, this));
slots.push_back(OPT_SUB("Audio/Next Line on Commit", &AudioTimingControllerKaraoke::OnAutoNextChange, this));
keyframes_provider.AddMarkerMovedListener(std::tr1::bind(std::tr1::ref(AnnounceMarkerMoved)));
video_position_provider.AddMarkerMovedListener(std::tr1::bind(std::tr1::ref(AnnounceMarkerMoved)));
Revert();
}
void AudioTimingControllerKaraoke::OnAutoCommitChange(agi::OptionValue const& opt) {
auto_commit = opt.GetBool();
}
void AudioTimingControllerKaraoke::OnAutoNextChange(agi::OptionValue const& opt) {
auto_next = opt.GetBool();
}
void AudioTimingControllerKaraoke::Next() {
++cur_syl;
if (cur_syl > markers.size()) {
--cur_syl;
c->selectionController->NextLine();
}
else {
AnnounceUpdatedPrimaryRange();
AnnounceUpdatedStyleRanges();
}
c->audioController->PlayPrimaryRange();
}
void AudioTimingControllerKaraoke::Prev() {
if (cur_syl == 0) {
AssDialogue *old_line = active_line;
c->selectionController->PrevLine();
if (old_line != active_line) {
cur_syl = markers.size();
AnnounceUpdatedPrimaryRange();
AnnounceUpdatedStyleRanges();
}
}
else {
--cur_syl;
AnnounceUpdatedPrimaryRange();
AnnounceUpdatedStyleRanges();
}
c->audioController->PlayPrimaryRange();
}
void AudioTimingControllerKaraoke::GetRenderingStyles(AudioRenderingStyleRanges &ranges) const
{
TimeRange sr = GetPrimaryPlaybackRange();
ranges.AddRange(sr.begin(), sr.end(), AudioStyle_Selected);
}
TimeRange AudioTimingControllerKaraoke::GetPrimaryPlaybackRange() const {
return TimeRange(
cur_syl > 0 ? markers[cur_syl - 1] : start_marker,
cur_syl < markers.size() ? markers[cur_syl] : end_marker);
}
TimeRange AudioTimingControllerKaraoke::GetIdealVisibleTimeRange() const {
return TimeRange(start_marker, end_marker);
}
void AudioTimingControllerKaraoke::GetMarkers(TimeRange const& range, AudioMarkerVector &out) const {
size_t i;
for (i = 0; i < markers.size() && markers[i] < range.begin(); ++i) ;
for (; i < markers.size() && markers[i] < range.end(); ++i)
out.push_back(&markers[i]);
if (range.contains(start_marker)) out.push_back(&start_marker);
if (range.contains(end_marker)) out.push_back(&end_marker);
keyframes_provider.GetMarkers(range, out);
video_position_provider.GetMarkers(range, out);
}
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);
file_changed_slot.Unblock();
}
void AudioTimingControllerKaraoke::Commit() {
if (!auto_commit)
DoCommit();
if (auto_next)
c->selectionController->NextLine();
}
void AudioTimingControllerKaraoke::Revert() {
active_line = c->selectionController->GetActiveLine();
cur_syl = 0;
commit_id = -1;
start_marker.Move(active_line->Start);
end_marker.Move(active_line->End);
markers.clear();
labels.clear();
markers.reserve(kara->size());
labels.reserve(kara->size());
for (AssKaraoke::iterator it = kara->begin(); it != kara->end(); ++it) {
if (it != kara->begin())
markers.push_back(KaraokeMarker(it->start_time, &separator_pen, AudioMarker::Feet_None));
labels.push_back(AudioLabel(it->text, TimeRange(it->start_time, it->start_time + it->duration)));
}
AnnounceUpdatedPrimaryRange();
AnnounceUpdatedStyleRanges();
AnnounceMarkerMoved();
}
bool AudioTimingControllerKaraoke::IsNearbyMarker(int ms, int sensitivity) const {
TimeRange range(ms - sensitivity, ms + sensitivity);
for (size_t i = 0; i < markers.size(); ++i)
if (range.contains(markers[i]))
return true;
return false;
}
AudioMarker *AudioTimingControllerKaraoke::OnLeftClick(int ms, int sensitivity, int) {
TimeRange range(ms - sensitivity, ms + sensitivity);
size_t syl = distance(markers.begin(), lower_bound(markers.begin(), markers.end(), ms));
if (syl < markers.size() && range.contains(markers[syl]))
return &markers[syl];
if (syl > 0 && range.contains(markers[syl - 1]))
return &markers[syl - 1];
cur_syl = syl;
AnnounceUpdatedPrimaryRange();
AnnounceUpdatedStyleRanges();
return 0;
}
void AudioTimingControllerKaraoke::OnMarkerDrag(AudioMarker *m, int new_position, int) {
KaraokeMarker *marker = static_cast<KaraokeMarker*>(m);
// No rearranging of syllables allowed
new_position = mid(
marker == &markers.front() ? start_marker.GetPosition() : (marker - 1)->GetPosition(),
new_position,
marker == &markers.back() ? end_marker.GetPosition() : (marker + 1)->GetPosition());
marker->Move(new_position);
size_t syl = marker - &markers.front() + 1;
kara->SetStartTime(syl, new_position);
if (syl == cur_syl || syl == cur_syl + 1) {
AnnounceUpdatedPrimaryRange();
AnnounceUpdatedStyleRanges();
}
AnnounceMarkerMoved();
labels[syl - 1].range = TimeRange(labels[syl - 1].range.begin(), new_position);
labels[syl].range = TimeRange(new_position, labels[syl].range.end());
AnnounceLabelChanged();
if (auto_commit)
DoCommit();
else
commit_id = -1;
}
void AudioTimingControllerKaraoke::GetLabels(TimeRange const& range, std::vector<AudioLabel> &out) const {
for (size_t i = 0; i < labels.size(); ++i) {
if (range.overlaps(labels[i].range))
out.push_back(labels[i]);
}
}