Aegisub/src/audio_display.h

302 lines
11 KiB
C
Raw Normal View History

2006-01-16 22:02:54 +01:00
// Copyright (c) 2005, Rodrigo Braz Monteiro
// Copyright (c) 2009-2010, Niels Martin Hansen
2006-01-16 22:02:54 +01:00
// 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/
2014-05-23 00:40:16 +02:00
//
#include <libaegisub/signal.h>
2006-01-16 22:02:54 +01:00
#include <chrono>
#include <cstdint>
#include <memory>
#include <wx/gdicmn.h>
#include <wx/string.h>
#include <wx/timer.h>
#include <wx/window.h>
namespace agi { class AudioProvider; }
namespace agi { struct Context; }
class AudioController;
class AudioRenderer;
class AudioRendererBitmapProvider;
class TimeRange;
// Helper classes used in implementation of the audio display
namespace {
class AudioDisplayScrollbar;
class AudioDisplayTimeline;
}
class AudioDisplayInteractionObject;
class AudioMarkerInteractionObject;
/// @class AudioDisplay
/// @brief Primary view/UI for interaction with audio timing
///
/// The audio display is the common view that allows the user to interact with the active
/// timing controller. The audio display also renders audio according to the audio controller
/// and the timing controller, using an audio renderer instance.
class AudioDisplay: public wxWindow {
agi::signal::Connection audio_open_connection;
std::vector<agi::signal::Connection> connections;
agi::Context *context;
/// The audio renderer manager
std::unique_ptr<AudioRenderer> audio_renderer;
/// The current audio renderer
std::unique_ptr<AudioRendererBitmapProvider> audio_renderer_provider;
2006-01-16 22:02:54 +01:00
/// The controller managing us
AudioController *controller = nullptr;
agi::AudioProvider *provider = nullptr;
/// Scrollbar helper object
std::unique_ptr<AudioDisplayScrollbar> scrollbar;
/// Timeline helper object
std::unique_ptr<AudioDisplayTimeline> timeline;
/// The interaction object for the last-dragged audio marker
std::unique_ptr<AudioMarkerInteractionObject> audio_marker;
/// Current object on display being dragged, if any
2013-12-12 01:29:48 +01:00
AudioDisplayInteractionObject *dragged_object = nullptr;
/// Change the dragged object and update mouse capture
void SetDraggedObject(AudioDisplayInteractionObject *new_obj);
/// Timer for scrolling when markers are dragged out of the displayed area
wxTimer scroll_timer;
wxTimer load_timer;
int64_t last_sample_decoded = 0;
/// Time at which audio loading began, for calculating loading speed
std::chrono::steady_clock::time_point audio_load_start_time;
/// Estimated speed of audio decoding in samples per ms
double audio_load_speed = 0.0;
/// Current position of the audio loading progress in absolute pixels
int audio_load_position = 0;
/// Leftmost pixel in the virtual audio image being displayed
2013-12-12 01:29:48 +01:00
int scroll_left = 0;
/// Total width of the audio in pixels
2013-12-12 01:29:48 +01:00
int pixel_audio_width = 0;
/// Horizontal zoom measured in millisecond per pixels
2013-12-12 01:29:48 +01:00
double ms_per_pixel = 0.;
/// Amplitude scaling ("vertical zoom") as a factor, 1.0 is neutral
2013-12-12 01:29:48 +01:00
float scale_amplitude = 1.f;
/// Top of the main audio area in pixels
2013-12-12 01:29:48 +01:00
int audio_top = 0;
/// Height of main audio area in pixels
2013-12-12 01:29:48 +01:00
int audio_height = 0;
/// Width of the audio marker feet in pixels
static const int foot_size = 6;
/// Zoom level given as a number, see SetZoomLevel for details
int zoom_level;
/// Absolute pixel position of the tracking cursor (mouse or playback)
2013-12-12 01:29:48 +01:00
int track_cursor_pos = -1;
/// Label to show by track cursor
wxString track_cursor_label;
/// Bounding rectangle last drawn track cursor label
wxRect track_cursor_label_rect;
/// @brief Move the tracking cursor
/// @param new_pos New absolute pixel position of the tracking cursor
/// @param show_time Display timestamp by the tracking cursor?
void SetTrackCursor(int new_pos, bool show_time);
/// @brief Remove the tracking cursor from the display
void RemoveTrackCursor();
/// Previous style ranges for optimizing redraw when ranges change
std::vector<std::pair<int, int>> style_ranges;
/// @brief Reload all rendering settings from Options and reset caches
///
/// This can be called if some rendering quality settings have been changed
/// in Options and need to be reloaded to take effect.
void ReloadRenderingSettings();
2006-01-16 22:02:54 +01:00
/// Paint the audio data for a time range
/// @param dc DC to paint to
/// @param updtime Time range to repaint
/// @param updrect Pixel range to repaint
void PaintAudio(wxDC &dc, TimeRange updtime, wxRect updrect);
/// Paint the markers in a time range
/// @param dc DC to paint to
/// @param updtime Time range to repaint
void PaintMarkers(wxDC &dc, TimeRange updtime);
/// Draw a single foot for a marker
/// @param dc DC to paint to
/// @param marker_x Position of the marker whose foot is being painted in pixels
/// @param dir -1 for left, 1 for right
void PaintFoot(wxDC &dc, int marker_x, int dir);
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
/// 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
void PaintLabels(wxDC &dc, TimeRange updtime);
/// Paint the track cursor
/// @param dc DC to paint to
void PaintTrackCursor(wxDC &dc);
/// Forward the mouse event to the appropriate child control, if any
/// @return Was the mouse event forwarded somewhere?
bool ForwardMouseEvent(wxMouseEvent &event);
/// wxWidgets paint event
2006-01-16 22:02:54 +01:00
void OnPaint(wxPaintEvent &event);
/// wxWidgets mouse input event
void OnMouseEvent(wxMouseEvent &event);
/// wxWidgets control size changed event
2006-01-16 22:02:54 +01:00
void OnSize(wxSizeEvent &event);
/// wxWidgets input focus changed event
void OnFocus(wxFocusEvent &event);
/// wxWidgets keypress event
void OnKeyDown(wxKeyEvent& event);
void OnScrollTimer(wxTimerEvent &event);
void OnLoadTimer(wxTimerEvent &);
void OnMouseEnter(wxMouseEvent&);
void OnMouseLeave(wxMouseEvent&);
int GetDuration() const;
void OnAudioOpen(agi::AudioProvider *provider);
void OnPlaybackPosition(int ms_position);
void OnSelectionChanged();
void OnStyleRangesChanged();
void OnTimingController();
void OnMarkerMoved();
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 OnTapMarkerChanged();
public:
AudioDisplay(wxWindow *parent, AudioController *controller, agi::Context *context);
~AudioDisplay();
/// @brief Scroll the audio display
/// @param pixel_amount Number of pixels to scroll the view
///
/// A positive amount moves the display to the right, making later parts of the audio visible.
void ScrollBy(int pixel_amount);
/// @brief Scroll the audio display
/// @param pixel_position Absolute pixel to put at left edge of the audio display
void ScrollPixelToLeft(int pixel_position);
/// @brief Scroll the audio display
/// @param range Time range to ensure is in view
///
/// If the entire range is already visible inside the display, nothing is
/// scrolled. If just one of the two endpoints is visible, the display is
/// scrolled such that the visible endpoint stays in view but more of the
/// rest of the range becomes visible.
///
/// If the entire range fits inside the display, the display is centered
/// over the range. For this calculation, the display is considered
/// smaller by some margins, see below.
///
/// If the range does not fit within the display with margins subtracted,
/// the start of the range is ensured visible and as much of the rest of
/// the range is brought into view.
///
/// For the purpose of this function, a 5 percent margin is assumed at each
/// end of the audio display such that a range endpoint that is ensured to
/// be in view never gets closer to the edge of the display than the
/// margin. The edge that is not ensured to be in view might be outside of
/// view or might be closer to the display edge than the
/// margin.
void ScrollTimeRangeInView(const TimeRange &range);
/// @brief Change the zoom level
/// @param new_zoom_level The new zoom level to use
///
/// A zoom level of 0 is the default zoom level, all other levels are based
/// on this. Negative zoom levels zoom out, positive zoom in.
///
/// The zoom levels generally go from +30 to -30. It is possible to zoom in
/// more than +30.
void SetZoomLevel(int new_zoom_level);
/// @brief Get the zoom level
/// @return The zoom level
///
/// See SetZoomLevel for a description of zoom levels.
2012-10-02 16:25:29 +02:00
int GetZoomLevel() const { return zoom_level; }
/// @brief Get a textual description of a zoom level
/// @param level The zoom level to describe
/// @return A translated string describing a zoom level
///
/// The zoom level description can tell the user details about how much audio is
/// actually displayed.
wxString GetZoomLevelDescription(int level) const;
/// @brief Get the zoom factor in percent for a zoom level
/// @param level The zoom level to get the factor of
/// @return The zoom factor in percent
///
/// Positive: 125, 150, 175, 200, 225, ...
///
/// Negative: 90, 80, 70, 60, 50, 45, 40, 35, 30, 25, 20, 19, 18, 17, ..., 1
///
/// Too negative numbers get clamped.
static int GetZoomLevelFactor(int level);
/// @brief Set amplitude scale factor
/// @param scale New amplitude scale factor, 1.0 is no scaling
void SetAmplitudeScale(float scale);
/// Get a time in milliseconds from an X coordinate relative to current scroll
int TimeFromRelativeX(int x) const { return int((scroll_left + x) * ms_per_pixel); }
/// Get a time in milliseconds from an absolute X coordinate
int TimeFromAbsoluteX(int x) const { return int(x * ms_per_pixel); }
/// Get an X coordinate relative to the current scroll from a time in milliseconds
int RelativeXFromTime(int ms) const { return int(ms / ms_per_pixel) - scroll_left; }
/// Get an absolute X coordinate from a time in milliseconds
int AbsoluteXFromTime(int ms) const { return int(ms / ms_per_pixel); }
2006-01-16 22:02:54 +01:00
};