// 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 #include #include #include #include #include #include #include #include #include #endif #include #include #include class AudioPlayer; class AudioProvider; namespace agi { struct Context; } // Declared below class AudioControllerAudioEventListener; class AudioControllerTimingEventListener; class AudioTimingController; class AudioMarker; class AudioMarkerProvider; typedef std::vector 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 &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 AnnounceAudioOpen; /// The current audio stream was closed agi::signal::Signal<> AnnounceAudioClose; /// Playback is in progress and the current position was updated agi::signal::Signal 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 audio output object AudioPlayer *player; /// The audio provider AudioProvider *provider; /// The current timing mode, if any; owned by the audio controller agi::scoped_ptr timing_controller; /// Provide keyframe data for audio displays agi::scoped_ptr keyframes_marker_provider; /// Provider current video position data for audio display agi::scoped_ptr 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(); #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 &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) }; /// @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"); };