236ea4261d
Originally committed to SVN as r5892.
443 lines
16 KiB
C++
443 lines
16 KiB
C++
// Copyright (c) 2009-2010, Niels Martin Hansen
|
|
// All rights reserved.
|
|
//
|
|
// Redistribution and use in source and binary forms, with or without
|
|
// modification, are permitted provided that the following conditions are met:
|
|
//
|
|
// * Redistributions of source code must retain the above copyright notice,
|
|
// this list of conditions and the following disclaimer.
|
|
// * Redistributions in binary form must reproduce the above copyright notice,
|
|
// this list of conditions and the following disclaimer in the documentation
|
|
// and/or other materials provided with the distribution.
|
|
// * Neither the name of the Aegisub Group nor the names of its contributors
|
|
// may be used to endorse or promote products derived from this software
|
|
// without specific prior written permission.
|
|
//
|
|
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
|
// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
|
// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
|
// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
|
// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
|
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
|
// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
|
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
|
// POSSIBILITY OF SUCH DAMAGE.
|
|
//
|
|
// Aegisub Project http://www.aegisub.org/
|
|
//
|
|
// $Id$
|
|
|
|
/// @file audio_controller.h
|
|
/// @see audio_controller.cpp
|
|
/// @ingroup audio_ui
|
|
|
|
#pragma once
|
|
|
|
#ifndef AGI_PRE
|
|
#include <memory>
|
|
#include <vector>
|
|
#include <set>
|
|
#include <stdint.h>
|
|
#include <assert.h>
|
|
#include <wx/event.h>
|
|
#include <wx/string.h>
|
|
#include <wx/timer.h>
|
|
#include <wx/pen.h>
|
|
#include <wx/power.h>
|
|
#endif
|
|
|
|
#include <libaegisub/exception.h>
|
|
#include <libaegisub/scoped_ptr.h>
|
|
#include <libaegisub/signal.h>
|
|
|
|
class AudioPlayer;
|
|
class AudioProvider;
|
|
namespace agi { struct Context; }
|
|
|
|
// Declared below
|
|
class AudioControllerAudioEventListener;
|
|
class AudioControllerTimingEventListener;
|
|
class AudioTimingController;
|
|
class AudioMarker;
|
|
class AudioMarkerProvider;
|
|
|
|
typedef std::vector<const AudioMarker*> AudioMarkerVector;
|
|
|
|
|
|
/// @class SampleRange
|
|
/// @brief Represents an immutable range of audio samples
|
|
class SampleRange {
|
|
int64_t _begin;
|
|
int64_t _end;
|
|
|
|
public:
|
|
/// @brief Constructor
|
|
/// @param begin Index of the first sample to include in the range
|
|
/// @param end Index of one past the last sample to include in the range
|
|
SampleRange(int64_t begin, int64_t end)
|
|
: _begin(begin)
|
|
, _end(end)
|
|
{
|
|
assert(end >= begin);
|
|
}
|
|
|
|
/// @brief Copy constructor, optionally adjusting the range
|
|
/// @param src The range to duplicate
|
|
/// @param begin_adjust Number of samples to add to the start of the range
|
|
/// @param end_adjust Number of samples to add to the end of the range
|
|
SampleRange(const SampleRange &src, int64_t begin_adjust = 0, int64_t end_adjust = 0)
|
|
{
|
|
_begin = src._begin + begin_adjust;
|
|
_end = src._end + end_adjust;
|
|
assert(_end >= _begin);
|
|
}
|
|
|
|
/// Get the number of samples in the range
|
|
int64_t length() const { return _end - _begin; }
|
|
/// Get the index of the first sample in the range
|
|
int64_t begin() const { return _begin; }
|
|
/// Get the index of one past the last sample in the range
|
|
int64_t end() const { return _end; }
|
|
|
|
/// Determine whether the range contains a given sample index
|
|
bool contains(int64_t sample) const { return sample >= begin() && sample < end(); }
|
|
|
|
/// Determine whether there is an overlap between two ranges
|
|
bool overlaps(const SampleRange &other) const
|
|
{
|
|
return other.contains(_begin) || contains(other._begin);
|
|
}
|
|
};
|
|
|
|
/// @class AudioMarkerProvider
|
|
/// @brief Abstract interface for audio marker providers
|
|
class AudioMarkerProvider {
|
|
protected:
|
|
/// One or more of the markers provided by this object have changed
|
|
agi::signal::Signal<> AnnounceMarkerMoved;
|
|
public:
|
|
/// Virtual destructor, does nothing
|
|
virtual ~AudioMarkerProvider() { }
|
|
|
|
/// @brief Return markers in a sample range
|
|
virtual void GetMarkers(const SampleRange &range, AudioMarkerVector &out) const = 0;
|
|
|
|
DEFINE_SIGNAL_ADDERS(AnnounceMarkerMoved, AddMarkerMovedListener)
|
|
};
|
|
|
|
/// @class AudioLabelProvider
|
|
/// @brief Abstract interface for audio label providers
|
|
class AudioLabelProvider {
|
|
protected:
|
|
/// One or more of the labels provided by this object have changed
|
|
agi::signal::Signal<> AnnounceLabelChanged;
|
|
public:
|
|
/// A label for a range of samples on the audio display
|
|
struct AudioLabel {
|
|
/// Text of the label
|
|
wxString text;
|
|
/// Range which this label applies to
|
|
SampleRange range;
|
|
AudioLabel(wxString const& text, SampleRange const& range) : text(text), range(range) { }
|
|
};
|
|
|
|
/// Virtual destructor, does nothing
|
|
virtual ~AudioLabelProvider() { }
|
|
|
|
/// @brief Get labels in a sample range
|
|
/// @param range Range of samples to get labels for
|
|
/// @param[out] out Vector which should be filled with the labels
|
|
virtual void GetLabels(SampleRange const& range, std::vector<AudioLabel> &out) const = 0;
|
|
|
|
DEFINE_SIGNAL_ADDERS(AnnounceLabelChanged, AddLabelChangedListener)
|
|
};
|
|
|
|
/// @class AudioController
|
|
/// @brief Manage an open audio stream and UI state for it
|
|
///
|
|
/// Keeps track of the UI interaction state of the open audio for a project,
|
|
/// i.e. what the current selection is, what movable markers are on the audio,
|
|
/// and any secondary non-movable markers that are present.
|
|
///
|
|
/// Changes in interaction are broadcast to all managed audio displays so they
|
|
/// can redraw, and the audio displays report all interactions back to the
|
|
/// controller. There is a one to many relationship between controller and
|
|
/// audio displays. There is at most one audio controller for an open
|
|
/// subtitling project.
|
|
///
|
|
/// Creates and destroys audio providers and players. This behaviour should at
|
|
/// some point be moved to a separate class, as it adds too many
|
|
/// responsibilities to this class, but at the time of writing, it would extend
|
|
/// the scope of reworking components too much.
|
|
///
|
|
/// There is not supposed to be a way to get direct access to the audio
|
|
/// providers or players owned by a controller. If some operation that isn't
|
|
/// possible in the existing design is needed, the controller should be
|
|
/// extended in some way to allow it.
|
|
class AudioController : public wxEvtHandler, public AudioMarkerProvider, public AudioLabelProvider {
|
|
/// Project context this controller belongs to
|
|
agi::Context *context;
|
|
|
|
/// Slot for subtitles save signal
|
|
agi::signal::Connection subtitle_save_slot;
|
|
|
|
/// A new audio stream was opened (and any previously open was closed)
|
|
agi::signal::Signal<AudioProvider*> AnnounceAudioOpen;
|
|
|
|
/// The current audio stream was closed
|
|
agi::signal::Signal<> AnnounceAudioClose;
|
|
|
|
/// Playback is in progress and the current position was updated
|
|
agi::signal::Signal<int64_t> AnnouncePlaybackPosition;
|
|
|
|
/// Playback has stopped
|
|
agi::signal::Signal<> AnnouncePlaybackStop;
|
|
|
|
/// The timing controller was replaced
|
|
agi::signal::Signal<> AnnounceTimingControllerChanged;
|
|
|
|
/// The selected time range changed
|
|
agi::signal::Signal<> AnnounceSelectionChanged;
|
|
|
|
/// The styling ranges have been updated by the timing controller
|
|
agi::signal::Signal<> AnnounceStyleRangesChanged;
|
|
|
|
/// The audio output object
|
|
AudioPlayer *player;
|
|
|
|
/// The audio provider
|
|
AudioProvider *provider;
|
|
|
|
/// The current timing mode, if any; owned by the audio controller
|
|
agi::scoped_ptr<AudioTimingController> timing_controller;
|
|
|
|
/// Provide keyframe data for audio displays
|
|
agi::scoped_ptr<AudioMarkerProvider> keyframes_marker_provider;
|
|
|
|
/// Provider current video position data for audio display
|
|
agi::scoped_ptr<AudioMarkerProvider> video_position_marker_provider;
|
|
|
|
/// The URL of the currently open audio, if any
|
|
wxString audio_url;
|
|
|
|
|
|
enum PlaybackMode {
|
|
PM_NotPlaying,
|
|
PM_Range,
|
|
PM_PrimaryRange,
|
|
PM_ToEnd
|
|
};
|
|
/// The current playback mode
|
|
PlaybackMode playback_mode;
|
|
|
|
|
|
/// Timer used for playback position updates
|
|
wxTimer playback_timer;
|
|
|
|
/// Event handler for the playback timer
|
|
void OnPlaybackTimer(wxTimerEvent &event);
|
|
|
|
/// @brief Timing controller signals primary playback range changed
|
|
void OnTimingControllerUpdatedPrimaryRange();
|
|
|
|
/// @brief Timing controller signals that the rendering style ranges have changed
|
|
void OnTimingControllerUpdatedStyleRanges();
|
|
|
|
/// Subtitles save slot which adds the audio uri to the subtitles
|
|
void OnSubtitlesSave();
|
|
|
|
/// Handler for the current audio player changing
|
|
void OnAudioPlayerChanged();
|
|
|
|
/// Handler for the current audio provider changing
|
|
void OnAudioProviderChanged();
|
|
|
|
#ifdef wxHAS_POWER_EVENTS
|
|
/// Handle computer going into suspend mode by stopping audio and closing device
|
|
void OnComputerSuspending(wxPowerEvent &event);
|
|
/// Handle computer resuming from suspend by re-opening the audio device
|
|
void OnComputerResuming(wxPowerEvent &event);
|
|
#endif
|
|
|
|
public:
|
|
|
|
/// @brief Constructor
|
|
AudioController(agi::Context *context);
|
|
|
|
/// @brief Destructor
|
|
~AudioController();
|
|
|
|
|
|
/// @brief Open an audio stream
|
|
/// @param url URL of the stream to open
|
|
///
|
|
/// The URL can either be a plain filename (with no qualifiers) or one
|
|
/// recognised by various providers.
|
|
void OpenAudio(const wxString &url);
|
|
|
|
/// @brief Closes the current audio stream
|
|
void CloseAudio();
|
|
|
|
/// @brief Determine whether audio is currently open
|
|
/// @return True if an audio stream is open and can be played back
|
|
bool IsAudioOpen() const;
|
|
|
|
/// @brief Get the URL for the current open audio stream
|
|
/// @return The URL for the audio stream
|
|
///
|
|
/// The returned URL can be passed into OpenAudio() later to open the same
|
|
/// stream again.
|
|
wxString GetAudioURL() const;
|
|
|
|
|
|
/// @brief Start or restart audio playback, playing a range
|
|
/// @param range The range of audio to play back
|
|
///
|
|
/// The end of the played back range may be requested changed, but is not
|
|
/// changed automatically from any other operations.
|
|
void PlayRange(const SampleRange &range);
|
|
|
|
/// @brief Start or restart audio playback, playing the primary playback range
|
|
///
|
|
/// If the primary playback range is updated during playback, the end of
|
|
/// the active playback range will be updated to match the new selection.
|
|
/// The playback end can not be changed in any other way.
|
|
void PlayPrimaryRange();
|
|
|
|
/// @brief Start or restart audio playback, playing from a point to the end of stream
|
|
/// @param start_sample Index of the sample to start playback at
|
|
///
|
|
/// Playback to end cannot be converted to a range playback like range
|
|
/// playback can, it will continue until the end is reached, it is stopped,
|
|
/// or restarted.
|
|
void PlayToEnd(int64_t start_sample);
|
|
|
|
/// @brief Stop all audio playback
|
|
void Stop();
|
|
|
|
/// @brief Determine whether playback is ongoing
|
|
/// @return True if audio is being played back
|
|
bool IsPlaying();
|
|
|
|
/// @brief Get the current playback position
|
|
/// @return Approximate current sample index being heard by the user
|
|
///
|
|
/// Returns 0 if playback is stopped. The return value is only approximate.
|
|
int64_t GetPlaybackPosition();
|
|
|
|
/// @brief If playing, restart playback from the specified position
|
|
/// @param new_position Sample index to restart playback from
|
|
///
|
|
/// This function can be used to re-synchronise audio playback to another
|
|
/// source that might not be able to keep up with the full speed, such as
|
|
/// video playback in high resolution or with complex subtitles.
|
|
///
|
|
/// This function only does something if audio is already playing.
|
|
void ResyncPlaybackPosition(int64_t new_position);
|
|
|
|
|
|
/// @brief Get the primary playback range
|
|
/// @return An immutable SampleRange object
|
|
SampleRange GetPrimaryPlaybackRange() const;
|
|
|
|
/// @brief Get all markers inside a range
|
|
/// @param range The sample range to retrieve markers for
|
|
/// @param markers Vector to fill found markers into
|
|
void GetMarkers(const SampleRange &range, AudioMarkerVector &markers) const;
|
|
|
|
/// @brief Get all labels inside a range
|
|
/// @param range The sample range to retrieve labels for
|
|
/// @param labels Vector to fill found labels into
|
|
void GetLabels(const SampleRange &range, std::vector<AudioLabel> &labels) const;
|
|
|
|
|
|
/// @brief Get the playback audio volume
|
|
/// @return The amplification factor for the audio
|
|
double GetVolume() const;
|
|
|
|
/// @brief Set the playback audio volume
|
|
/// @param volume The new amplification factor for the audio
|
|
void SetVolume(double volume);
|
|
|
|
|
|
/// @brief Return the current audio provider
|
|
/// @return A const pointer to the current audio provider
|
|
const AudioProvider * GetAudioProvider() const { return provider; }
|
|
|
|
|
|
/// @brief Return the current timing controller
|
|
/// @return The current timing controller or 0
|
|
AudioTimingController * GetTimingController() const { return timing_controller.get(); }
|
|
|
|
/// @brief Change the current timing controller
|
|
/// @param new_mode The new timing controller or 0. This may be the same
|
|
/// object as the current timing controller, to signal that the timing
|
|
/// controller has changed the object being timed, e.g. changed to a new
|
|
/// dialogue line.
|
|
void SetTimingController(AudioTimingController *new_controller);
|
|
|
|
/// @brief Convert a count of audio samples to a time in milliseconds
|
|
/// @param samples Sample count to convert
|
|
/// @return The number of milliseconds equivalent to the sample-count, rounded down
|
|
int64_t MillisecondsFromSamples(int64_t samples) const;
|
|
|
|
/// @brief Convert a time in milliseconds to a count of audio samples
|
|
/// @param ms Time in milliseconds to convert
|
|
/// @return The index of the first sample that is wholly inside the millisecond
|
|
int64_t SamplesFromMilliseconds(int64_t ms) const;
|
|
|
|
/// @brief Save a portion of the decoded loaded audio to a wav file
|
|
/// @param filename File to save to
|
|
/// @param range Range of samples to save
|
|
void SaveClip(wxString const& filename, SampleRange const& range) const;
|
|
|
|
DEFINE_SIGNAL_ADDERS(AnnounceAudioOpen, AddAudioOpenListener)
|
|
DEFINE_SIGNAL_ADDERS(AnnounceAudioClose, AddAudioCloseListener)
|
|
DEFINE_SIGNAL_ADDERS(AnnouncePlaybackPosition, AddPlaybackPositionListener)
|
|
DEFINE_SIGNAL_ADDERS(AnnouncePlaybackStop, AddPlaybackStopListener)
|
|
DEFINE_SIGNAL_ADDERS(AnnounceTimingControllerChanged, AddTimingControllerListener)
|
|
DEFINE_SIGNAL_ADDERS(AnnounceSelectionChanged, AddSelectionChangedListener)
|
|
DEFINE_SIGNAL_ADDERS(AnnounceStyleRangesChanged, AddStyleRangesChangedListener)
|
|
};
|
|
|
|
/// @class AudioMarker
|
|
/// @brief A marker on the audio display
|
|
class AudioMarker {
|
|
public:
|
|
|
|
/// Describe which directions a marker has feet in
|
|
enum FeetStyle {
|
|
Feet_None = 0,
|
|
Feet_Left,
|
|
Feet_Right,
|
|
Feet_Both // Conveniently Feet_Left|Feet_Right
|
|
};
|
|
|
|
/// @brief Get the marker's position
|
|
/// @return The marker's position in samples
|
|
virtual int64_t GetPosition() const = 0;
|
|
|
|
/// @brief Get the marker's drawing style
|
|
/// @return A pen object describing the marker's drawing style
|
|
virtual wxPen GetStyle() const = 0;
|
|
|
|
/// @brief Get the marker's feet style
|
|
/// @return The marker's feet style
|
|
virtual FeetStyle GetFeet() const = 0;
|
|
|
|
/// @brief Retrieve whether this marker participates in snapping
|
|
/// @return True if this marker may snap to other snappable markers
|
|
///
|
|
/// If a marker being dragged returns true from this method, and another
|
|
/// marker which also returns true from this method is within range, the
|
|
/// marker being dragged will be positioned at the position of the other
|
|
/// marker if it is released while it is inside snapping range.
|
|
virtual bool CanSnap() const = 0;
|
|
};
|
|
|
|
namespace agi {
|
|
DEFINE_BASE_EXCEPTION(AudioControllerError, Exception);
|
|
DEFINE_SIMPLE_EXCEPTION(AudioOpenError, AudioControllerError, "audio_controller/open_failed");
|
|
};
|