2006-01-16 22:02:54 +01:00
|
|
|
// Copyright (c) 2005, Rodrigo Braz Monteiro
|
2010-12-08 04:36:10 +01:00
|
|
|
// 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.
|
|
|
|
//
|
2009-07-29 07:43:02 +02:00
|
|
|
// Aegisub Project http://www.aegisub.org/
|
|
|
|
|
2012-02-02 00:59:12 +01:00
|
|
|
#include "audio_display.h"
|
|
|
|
|
2010-12-08 04:36:10 +01:00
|
|
|
#include "audio_controller.h"
|
|
|
|
#include "audio_renderer.h"
|
|
|
|
#include "audio_renderer_spectrum.h"
|
|
|
|
#include "audio_renderer_waveform.h"
|
|
|
|
#include "audio_timing.h"
|
2011-11-30 22:04:37 +01:00
|
|
|
#include "compat.h"
|
2014-05-29 17:28:37 +02:00
|
|
|
#include "format.h"
|
2011-08-31 06:17:31 +02:00
|
|
|
#include "include/aegisub/context.h"
|
2011-01-18 00:54:05 +01:00
|
|
|
#include "include/aegisub/hotkey.h"
|
2013-01-07 02:50:09 +01:00
|
|
|
#include "options.h"
|
2014-05-22 01:23:28 +02:00
|
|
|
#include "project.h"
|
2009-09-10 15:06:40 +02:00
|
|
|
#include "utils.h"
|
2014-05-22 01:23:28 +02:00
|
|
|
#include "video_controller.h"
|
2009-09-10 15:06:40 +02:00
|
|
|
|
2014-07-06 16:28:58 +02:00
|
|
|
#include <libaegisub/ass/time.h>
|
2014-07-09 16:22:49 +02:00
|
|
|
#include <libaegisub/audio/provider.h>
|
2014-04-23 22:53:24 +02:00
|
|
|
#include <libaegisub/make_unique.h>
|
2013-06-08 06:19:40 +02:00
|
|
|
|
|
|
|
#include <algorithm>
|
|
|
|
|
|
|
|
#include <wx/dcbuffer.h>
|
|
|
|
#include <wx/mousestate.h>
|
|
|
|
|
2014-12-31 04:16:35 +01:00
|
|
|
/// @class AudioDisplayInteractionObject
|
|
|
|
/// @brief Interface for objects on the audio display that can respond to mouse events
|
|
|
|
class AudioDisplayInteractionObject {
|
|
|
|
public:
|
|
|
|
/// @brief The user is interacting with the object using the mouse
|
|
|
|
/// @param event Mouse event data
|
|
|
|
/// @return True to take mouse capture, false to release mouse capture
|
|
|
|
///
|
|
|
|
/// Assuming no object has the mouse capture, the audio display uses other methods
|
|
|
|
/// in the object implementing this interface to determine whether a mouse event
|
|
|
|
/// should go to the object. If the mouse event goes to the object, this method
|
|
|
|
/// is called.
|
|
|
|
///
|
|
|
|
/// If this method returns true, the audio display takes the mouse capture and
|
|
|
|
/// stores a pointer to the AudioDisplayInteractionObject interface for the object
|
|
|
|
/// and redirects the next mouse event to that object.
|
|
|
|
///
|
|
|
|
/// If the object that has the mouse capture returns false from this method, the
|
|
|
|
/// capture is released and regular processing is done for the next event.
|
|
|
|
///
|
|
|
|
/// If the object does not have mouse capture and returns false from this method,
|
|
|
|
/// no capture is taken or released and regular processing is done for the next
|
|
|
|
/// mouse event.
|
|
|
|
virtual bool OnMouseEvent(wxMouseEvent &event) = 0;
|
|
|
|
|
|
|
|
/// @brief Destructor
|
|
|
|
///
|
|
|
|
/// Empty virtual destructor for the cases that need it.
|
|
|
|
virtual ~AudioDisplayInteractionObject() = default;
|
|
|
|
};
|
|
|
|
|
2014-04-22 21:34:20 +02:00
|
|
|
namespace {
|
2011-11-30 22:04:37 +01:00
|
|
|
/// @brief Colourscheme-based UI colour provider
|
|
|
|
///
|
|
|
|
/// This class provides UI colours corresponding to the supplied audio colour
|
|
|
|
/// scheme.
|
|
|
|
///
|
|
|
|
/// SetColourScheme must be called to set the active colour scheme before
|
|
|
|
/// colours can be retrieved
|
|
|
|
class UIColours {
|
|
|
|
wxColour light_colour; ///< Light unfocused colour from the colour scheme
|
|
|
|
wxColour dark_colour; ///< Dark unfocused colour from the colour scheme
|
|
|
|
wxColour sel_colour; ///< Selection unfocused colour from the colour scheme
|
|
|
|
wxColour light_focused_colour; ///< Light focused colour from the colour scheme
|
|
|
|
wxColour dark_focused_colour; ///< Dark focused colour from the colour scheme
|
|
|
|
wxColour sel_focused_colour; ///< Selection focused colour from the colour scheme
|
|
|
|
|
2014-04-22 21:34:20 +02:00
|
|
|
bool focused = false; ///< Use the focused colours?
|
2011-11-30 22:04:37 +01:00
|
|
|
public:
|
|
|
|
/// Set the colour scheme to load colours from
|
|
|
|
/// @param name Name of the colour scheme
|
|
|
|
void SetColourScheme(std::string const& name)
|
|
|
|
{
|
|
|
|
std::string opt_prefix = "Colour/Schemes/" + name + "/UI/";
|
2012-10-26 16:09:14 +02:00
|
|
|
light_colour = to_wx(OPT_GET(opt_prefix + "Light")->GetColor());
|
|
|
|
dark_colour = to_wx(OPT_GET(opt_prefix + "Dark")->GetColor());
|
|
|
|
sel_colour = to_wx(OPT_GET(opt_prefix + "Selection")->GetColor());
|
2011-11-30 22:04:37 +01:00
|
|
|
|
|
|
|
opt_prefix = "Colour/Schemes/" + name + "/UI Focused/";
|
2012-10-26 16:09:14 +02:00
|
|
|
light_focused_colour = to_wx(OPT_GET(opt_prefix + "Light")->GetColor());
|
|
|
|
dark_focused_colour = to_wx(OPT_GET(opt_prefix + "Dark")->GetColor());
|
|
|
|
sel_focused_colour = to_wx(OPT_GET(opt_prefix + "Selection")->GetColor());
|
2011-11-30 22:04:37 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
/// Set whether to use the focused or unfocused colours
|
|
|
|
/// @param focused If true, focused colours will be returned
|
|
|
|
void SetFocused(bool focused) { this->focused = focused; }
|
|
|
|
|
|
|
|
/// Get the current Light colour
|
|
|
|
wxColour Light() const { return focused ? light_focused_colour : light_colour; }
|
|
|
|
/// Get the current Dark colour
|
|
|
|
wxColour Dark() const { return focused ? dark_focused_colour : dark_colour; }
|
|
|
|
/// Get the current Selection colour
|
|
|
|
wxColour Selection() const { return focused ? sel_focused_colour : sel_colour; }
|
|
|
|
};
|
|
|
|
|
2014-03-13 02:39:07 +01:00
|
|
|
class AudioDisplayScrollbar final : public AudioDisplayInteractionObject {
|
2012-05-01 04:49:58 +02:00
|
|
|
static const int height = 15;
|
2011-09-30 22:41:10 +02:00
|
|
|
static const int min_width = 10;
|
2006-01-16 22:02:54 +01:00
|
|
|
|
2010-12-08 04:36:10 +01:00
|
|
|
wxRect bounds;
|
|
|
|
wxRect thumb;
|
2006-01-16 22:02:54 +01:00
|
|
|
|
2014-04-22 21:34:20 +02:00
|
|
|
bool dragging = false; ///< user is dragging with the primary mouse button
|
2011-11-30 22:04:37 +01:00
|
|
|
|
2014-04-22 21:34:20 +02:00
|
|
|
int data_length = 1; ///< total amount of data in control
|
|
|
|
int page_length = 1; ///< amount of data in one page
|
|
|
|
int position = 0; ///< first item displayed
|
2008-11-14 02:21:17 +01:00
|
|
|
|
2014-04-22 21:34:20 +02:00
|
|
|
int sel_start = -1; ///< first data item in selection
|
|
|
|
int sel_length = 0; ///< number of data items in selection
|
2008-11-14 02:21:17 +01:00
|
|
|
|
2011-11-30 22:04:37 +01:00
|
|
|
UIColours colours; ///< Colour provider
|
2006-01-16 22:02:54 +01:00
|
|
|
|
2011-11-30 22:04:37 +01:00
|
|
|
/// Containing display to send scroll events to
|
2010-12-08 04:36:10 +01:00
|
|
|
AudioDisplay *display;
|
2006-01-16 22:02:54 +01:00
|
|
|
|
2010-12-08 04:36:10 +01:00
|
|
|
// Recalculate thumb bounds from position and length data
|
|
|
|
void RecalculateThumb()
|
|
|
|
{
|
2012-01-13 21:59:35 +01:00
|
|
|
thumb.width = std::max<int>(min_width, (int64_t)bounds.width * page_length / data_length);
|
2010-12-08 04:36:10 +01:00
|
|
|
thumb.height = height;
|
2012-01-13 21:59:35 +01:00
|
|
|
thumb.x = int((int64_t)bounds.width * position / data_length);
|
2010-12-08 04:36:10 +01:00
|
|
|
thumb.y = bounds.y;
|
|
|
|
}
|
2006-01-16 22:02:54 +01:00
|
|
|
|
2010-12-08 04:36:10 +01:00
|
|
|
public:
|
2011-09-15 07:16:18 +02:00
|
|
|
AudioDisplayScrollbar(AudioDisplay *display)
|
2014-04-22 21:34:20 +02:00
|
|
|
: display(display)
|
2010-12-08 04:36:10 +01:00
|
|
|
{
|
2006-01-16 22:02:54 +01:00
|
|
|
}
|
|
|
|
|
2011-11-30 22:04:37 +01:00
|
|
|
/// The audio display has changed size
|
2010-12-08 04:36:10 +01:00
|
|
|
void SetDisplaySize(const wxSize &display_size)
|
|
|
|
{
|
|
|
|
bounds.x = 0;
|
|
|
|
bounds.y = display_size.y - height;
|
|
|
|
bounds.width = display_size.x;
|
|
|
|
bounds.height = height;
|
|
|
|
page_length = display_size.x;
|
2006-01-16 22:02:54 +01:00
|
|
|
|
2010-12-08 04:36:10 +01:00
|
|
|
RecalculateThumb();
|
2006-01-16 22:02:54 +01:00
|
|
|
}
|
|
|
|
|
2011-11-30 22:04:37 +01:00
|
|
|
void SetColourScheme(std::string const& name)
|
|
|
|
{
|
|
|
|
colours.SetColourScheme(name);
|
|
|
|
}
|
2007-04-07 04:39:18 +02:00
|
|
|
|
2011-09-15 07:16:18 +02:00
|
|
|
const wxRect & GetBounds() const { return bounds; }
|
|
|
|
int GetPosition() const { return position; }
|
2006-01-16 22:02:54 +01:00
|
|
|
|
2010-12-08 04:36:10 +01:00
|
|
|
int SetPosition(int new_position)
|
|
|
|
{
|
|
|
|
// These two conditionals can't be swapped, otherwise the position can become
|
|
|
|
// negative if the entire data is shorter than one page.
|
|
|
|
if (new_position + page_length >= data_length)
|
|
|
|
new_position = data_length - page_length - 1;
|
|
|
|
if (new_position < 0)
|
|
|
|
new_position = 0;
|
|
|
|
|
2011-09-15 07:16:18 +02:00
|
|
|
position = new_position;
|
|
|
|
RecalculateThumb();
|
2010-12-08 04:36:10 +01:00
|
|
|
|
|
|
|
return position;
|
2008-01-14 02:18:24 +01:00
|
|
|
}
|
|
|
|
|
2010-12-08 04:36:10 +01:00
|
|
|
void SetSelection(int new_start, int new_length)
|
|
|
|
{
|
2012-01-13 22:17:40 +01:00
|
|
|
sel_start = (int64_t)new_start * bounds.width / data_length;
|
|
|
|
sel_length = (int64_t)new_length * bounds.width / data_length;
|
2006-01-16 22:02:54 +01:00
|
|
|
}
|
|
|
|
|
2010-12-08 04:36:10 +01:00
|
|
|
void ChangeLengths(int new_data_length, int new_page_length)
|
|
|
|
{
|
|
|
|
data_length = new_data_length;
|
|
|
|
page_length = new_page_length;
|
2006-01-16 22:02:54 +01:00
|
|
|
|
2010-12-08 04:36:10 +01:00
|
|
|
RecalculateThumb();
|
2006-01-16 22:02:54 +01:00
|
|
|
}
|
|
|
|
|
2013-11-21 18:13:36 +01:00
|
|
|
bool OnMouseEvent(wxMouseEvent &event) override
|
2010-12-08 04:36:10 +01:00
|
|
|
{
|
|
|
|
if (event.LeftIsDown())
|
|
|
|
{
|
|
|
|
const int thumb_left = event.GetPosition().x - thumb.width/2;
|
|
|
|
const int data_length_less_page = data_length - page_length;
|
|
|
|
const int shaft_length_less_thumb = bounds.width - thumb.width;
|
|
|
|
|
2012-01-13 21:59:35 +01:00
|
|
|
display->ScrollPixelToLeft((int64_t)data_length_less_page * thumb_left / shaft_length_less_thumb);
|
2010-12-08 04:36:10 +01:00
|
|
|
|
|
|
|
dragging = true;
|
2006-01-16 22:02:54 +01:00
|
|
|
}
|
2010-12-08 04:36:10 +01:00
|
|
|
else if (event.LeftUp())
|
|
|
|
{
|
|
|
|
dragging = false;
|
2006-01-16 22:02:54 +01:00
|
|
|
}
|
2007-04-07 04:39:18 +02:00
|
|
|
|
2010-12-08 04:36:10 +01:00
|
|
|
return dragging;
|
2006-01-16 22:02:54 +01:00
|
|
|
}
|
|
|
|
|
2014-04-22 21:34:20 +02:00
|
|
|
void Paint(wxDC &dc, bool has_focus, int load_progress)
|
2010-12-08 04:36:10 +01:00
|
|
|
{
|
2011-11-30 22:04:37 +01:00
|
|
|
colours.SetFocused(has_focus);
|
2007-04-13 03:29:05 +02:00
|
|
|
|
2011-11-30 22:04:37 +01:00
|
|
|
dc.SetPen(wxPen(colours.Light()));
|
|
|
|
dc.SetBrush(wxBrush(colours.Dark()));
|
2010-12-08 04:36:10 +01:00
|
|
|
dc.DrawRectangle(bounds);
|
2007-04-13 03:29:05 +02:00
|
|
|
|
2010-12-08 04:36:10 +01:00
|
|
|
if (sel_length > 0 && sel_start >= 0)
|
|
|
|
{
|
2011-11-30 22:04:37 +01:00
|
|
|
dc.SetPen(wxPen(colours.Selection()));
|
|
|
|
dc.SetBrush(wxBrush(colours.Selection()));
|
2012-01-13 22:17:40 +01:00
|
|
|
dc.DrawRectangle(wxRect(sel_start, bounds.y, sel_length, bounds.height));
|
2007-04-13 03:29:05 +02:00
|
|
|
}
|
|
|
|
|
2011-11-30 22:04:37 +01:00
|
|
|
dc.SetPen(wxPen(colours.Light()));
|
2010-12-08 04:36:10 +01:00
|
|
|
dc.SetBrush(*wxTRANSPARENT_BRUSH);
|
|
|
|
dc.DrawRectangle(bounds);
|
|
|
|
|
2014-04-22 21:34:20 +02:00
|
|
|
if (load_progress > 0 && load_progress < data_length)
|
|
|
|
{
|
|
|
|
wxRect marker(
|
|
|
|
(int64_t)bounds.width * load_progress / data_length - 25, bounds.y + 1,
|
|
|
|
25, bounds.height - 2);
|
|
|
|
dc.GradientFillLinear(marker, colours.Dark(), colours.Light());
|
|
|
|
}
|
|
|
|
|
2011-11-30 22:04:37 +01:00
|
|
|
dc.SetPen(wxPen(colours.Light()));
|
|
|
|
dc.SetBrush(wxBrush(colours.Light()));
|
2010-12-08 04:36:10 +01:00
|
|
|
dc.DrawRectangle(thumb);
|
|
|
|
}
|
|
|
|
};
|
2011-11-30 22:04:37 +01:00
|
|
|
|
2011-11-08 05:53:51 +01:00
|
|
|
const int AudioDisplayScrollbar::min_width;
|
2010-12-08 04:36:10 +01:00
|
|
|
|
2014-03-13 02:39:07 +01:00
|
|
|
class AudioDisplayTimeline final : public AudioDisplayInteractionObject {
|
2014-04-22 21:34:20 +02:00
|
|
|
int duration = 0; ///< Total duration in ms
|
|
|
|
double ms_per_pixel = 1.0; ///< Milliseconds per pixel
|
|
|
|
int pixel_left = 0; ///< Leftmost visible pixel (i.e. scroll position)
|
2010-12-08 04:36:10 +01:00
|
|
|
|
|
|
|
wxRect bounds;
|
|
|
|
|
|
|
|
wxPoint drag_lastpos;
|
2014-04-22 21:34:20 +02:00
|
|
|
bool dragging = false;
|
2010-12-08 04:36:10 +01:00
|
|
|
|
|
|
|
enum Scale {
|
|
|
|
Sc_Millisecond,
|
|
|
|
Sc_Centisecond,
|
|
|
|
Sc_Decisecond,
|
|
|
|
Sc_Second,
|
|
|
|
Sc_Decasecond,
|
|
|
|
Sc_Minute,
|
|
|
|
Sc_Decaminute,
|
|
|
|
Sc_Hour,
|
|
|
|
Sc_Decahour, // If anyone needs this they should reconsider their project
|
|
|
|
Sc_MAX = Sc_Decahour
|
|
|
|
};
|
|
|
|
Scale scale_minor;
|
2011-11-30 22:04:46 +01:00
|
|
|
int scale_major_modulo; ///< If minor_scale_mark_index % scale_major_modulo == 0 the mark is a major mark
|
|
|
|
double scale_minor_divisor; ///< Absolute scale-mark index multiplied by this number gives sample index for scale mark
|
2010-12-08 04:36:10 +01:00
|
|
|
|
2012-02-02 00:58:58 +01:00
|
|
|
AudioDisplay *display; ///< Containing audio display
|
2010-12-08 04:36:10 +01:00
|
|
|
|
2011-11-30 22:04:46 +01:00
|
|
|
UIColours colours; ///< Colour provider
|
|
|
|
|
2010-12-08 04:36:10 +01:00
|
|
|
public:
|
2011-11-30 22:04:46 +01:00
|
|
|
AudioDisplayTimeline(AudioDisplay *display)
|
2014-04-22 21:34:20 +02:00
|
|
|
: display(display)
|
2010-12-08 04:36:10 +01:00
|
|
|
{
|
|
|
|
int width, height;
|
2011-09-28 21:43:11 +02:00
|
|
|
display->GetTextExtent("0123456789:.", &width, &height);
|
2012-02-02 00:58:58 +01:00
|
|
|
bounds.height = height + 4;
|
2010-12-08 04:36:10 +01:00
|
|
|
}
|
|
|
|
|
2011-11-30 22:04:46 +01:00
|
|
|
void SetColourScheme(std::string const& name)
|
|
|
|
{
|
|
|
|
colours.SetColourScheme(name);
|
|
|
|
}
|
|
|
|
|
2010-12-08 04:36:10 +01:00
|
|
|
void SetDisplaySize(const wxSize &display_size)
|
|
|
|
{
|
|
|
|
// The size is without anything that goes below the timeline (like scrollbar)
|
|
|
|
bounds.width = display_size.x;
|
|
|
|
bounds.x = 0;
|
|
|
|
bounds.y = 0;
|
|
|
|
}
|
|
|
|
|
2012-02-02 00:58:58 +01:00
|
|
|
int GetHeight() const { return bounds.height; }
|
2011-09-15 07:16:18 +02:00
|
|
|
const wxRect & GetBounds() const { return bounds; }
|
2010-12-08 04:36:10 +01:00
|
|
|
|
2012-02-02 00:58:58 +01:00
|
|
|
void ChangeAudio(int new_duration)
|
2010-12-08 04:36:10 +01:00
|
|
|
{
|
2012-02-02 00:58:58 +01:00
|
|
|
duration = new_duration;
|
2010-12-08 04:36:10 +01:00
|
|
|
}
|
|
|
|
|
2012-02-02 00:58:58 +01:00
|
|
|
void ChangeZoom(double new_ms_per_pixel)
|
2010-12-08 04:36:10 +01:00
|
|
|
{
|
2012-02-02 00:58:58 +01:00
|
|
|
ms_per_pixel = new_ms_per_pixel;
|
2010-12-08 04:36:10 +01:00
|
|
|
|
2012-02-02 00:58:58 +01:00
|
|
|
double px_sec = 1000.0 / ms_per_pixel;
|
2010-12-08 04:36:10 +01:00
|
|
|
|
|
|
|
if (px_sec > 3000) {
|
|
|
|
scale_minor = Sc_Millisecond;
|
2012-02-02 00:58:58 +01:00
|
|
|
scale_minor_divisor = 1.0;
|
2010-12-08 04:36:10 +01:00
|
|
|
scale_major_modulo = 10;
|
|
|
|
} else if (px_sec > 300) {
|
|
|
|
scale_minor = Sc_Centisecond;
|
2012-02-02 00:58:58 +01:00
|
|
|
scale_minor_divisor = 10.0;
|
2010-12-08 04:36:10 +01:00
|
|
|
scale_major_modulo = 10;
|
|
|
|
} else if (px_sec > 30) {
|
|
|
|
scale_minor = Sc_Decisecond;
|
2012-02-02 00:58:58 +01:00
|
|
|
scale_minor_divisor = 100.0;
|
2010-12-08 04:36:10 +01:00
|
|
|
scale_major_modulo = 10;
|
|
|
|
} else if (px_sec > 3) {
|
|
|
|
scale_minor = Sc_Second;
|
2012-02-02 00:58:58 +01:00
|
|
|
scale_minor_divisor = 1000.0;
|
2010-12-08 04:36:10 +01:00
|
|
|
scale_major_modulo = 10;
|
|
|
|
} else if (px_sec > 1.0/3.0) {
|
|
|
|
scale_minor = Sc_Decasecond;
|
2012-02-02 00:58:58 +01:00
|
|
|
scale_minor_divisor = 10000.0;
|
2010-12-08 04:36:10 +01:00
|
|
|
scale_major_modulo = 6;
|
|
|
|
} else if (px_sec > 1.0/9.0) {
|
|
|
|
scale_minor = Sc_Minute;
|
2012-02-02 00:58:58 +01:00
|
|
|
scale_minor_divisor = 60000.0;
|
2010-12-08 04:36:10 +01:00
|
|
|
scale_major_modulo = 10;
|
|
|
|
} else if (px_sec > 1.0/90.0) {
|
|
|
|
scale_minor = Sc_Decaminute;
|
2012-02-02 00:58:58 +01:00
|
|
|
scale_minor_divisor = 600000.0;
|
2010-12-08 04:36:10 +01:00
|
|
|
scale_major_modulo = 6;
|
|
|
|
} else {
|
|
|
|
scale_minor = Sc_Hour;
|
2012-02-02 00:58:58 +01:00
|
|
|
scale_minor_divisor = 3600000.0;
|
2010-12-08 04:36:10 +01:00
|
|
|
scale_major_modulo = 10;
|
2007-04-07 04:39:18 +02:00
|
|
|
}
|
|
|
|
}
|
2006-03-06 01:45:24 +01:00
|
|
|
|
2010-12-08 04:36:10 +01:00
|
|
|
void SetPosition(int new_pixel_left)
|
|
|
|
{
|
2011-09-15 07:16:18 +02:00
|
|
|
pixel_left = std::max(new_pixel_left, 0);
|
2006-01-16 22:02:54 +01:00
|
|
|
}
|
|
|
|
|
2013-11-21 18:13:36 +01:00
|
|
|
bool OnMouseEvent(wxMouseEvent &event) override
|
2010-12-08 04:36:10 +01:00
|
|
|
{
|
|
|
|
if (event.LeftDown())
|
|
|
|
{
|
|
|
|
drag_lastpos = event.GetPosition();
|
|
|
|
dragging = true;
|
|
|
|
}
|
|
|
|
else if (event.LeftIsDown())
|
|
|
|
{
|
2011-09-15 07:16:18 +02:00
|
|
|
display->ScrollPixelToLeft(pixel_left - event.GetPosition().x + drag_lastpos.x);
|
2006-01-16 22:02:54 +01:00
|
|
|
|
2010-12-08 04:36:10 +01:00
|
|
|
drag_lastpos = event.GetPosition();
|
|
|
|
dragging = true;
|
2006-01-16 22:02:54 +01:00
|
|
|
}
|
2010-12-08 04:36:10 +01:00
|
|
|
else if (event.LeftUp())
|
|
|
|
{
|
|
|
|
dragging = false;
|
2006-01-16 22:02:54 +01:00
|
|
|
}
|
|
|
|
|
2010-12-08 04:36:10 +01:00
|
|
|
return dragging;
|
2006-01-16 22:02:54 +01:00
|
|
|
}
|
|
|
|
|
2010-12-08 04:36:10 +01:00
|
|
|
void Paint(wxDC &dc)
|
|
|
|
{
|
|
|
|
int bottom = bounds.y + bounds.height;
|
2006-01-16 22:02:54 +01:00
|
|
|
|
2010-12-08 04:36:10 +01:00
|
|
|
// Background
|
2011-11-30 22:04:46 +01:00
|
|
|
dc.SetPen(wxPen(colours.Dark()));
|
|
|
|
dc.SetBrush(wxBrush(colours.Dark()));
|
2010-12-08 04:36:10 +01:00
|
|
|
dc.DrawRectangle(bounds);
|
2007-01-07 05:44:11 +01:00
|
|
|
|
2010-12-08 04:36:10 +01:00
|
|
|
// Top line
|
2011-11-30 22:04:46 +01:00
|
|
|
dc.SetPen(wxPen(colours.Light()));
|
2010-12-08 04:36:10 +01:00
|
|
|
dc.DrawLine(bounds.x, bottom-1, bounds.x+bounds.width, bottom-1);
|
2008-01-22 21:36:07 +01:00
|
|
|
|
2010-12-08 04:36:10 +01:00
|
|
|
// Prepare for writing text
|
2011-11-30 22:04:46 +01:00
|
|
|
dc.SetTextBackground(colours.Dark());
|
|
|
|
dc.SetTextForeground(colours.Light());
|
2006-08-27 21:54:51 +02:00
|
|
|
|
2010-12-08 04:36:10 +01:00
|
|
|
// Figure out the first scale mark to show
|
2012-02-02 00:58:58 +01:00
|
|
|
int ms_left = int(pixel_left * ms_per_pixel);
|
|
|
|
int next_scale_mark = int(ms_left / scale_minor_divisor);
|
|
|
|
if (next_scale_mark * scale_minor_divisor < ms_left)
|
2010-12-08 04:36:10 +01:00
|
|
|
next_scale_mark += 1;
|
2012-02-02 00:58:58 +01:00
|
|
|
assert(next_scale_mark * scale_minor_divisor >= ms_left);
|
2006-01-16 22:02:54 +01:00
|
|
|
|
2010-12-08 04:36:10 +01:00
|
|
|
// Draw scale marks
|
|
|
|
int next_scale_mark_pos;
|
|
|
|
int last_text_right = -1;
|
|
|
|
int last_hour = -1, last_minute = -1;
|
2012-02-02 00:58:58 +01:00
|
|
|
if (duration < 3600) last_hour = 0; // Trick to only show hours if audio is longer than 1 hour
|
2010-12-08 04:36:10 +01:00
|
|
|
do {
|
2012-02-02 00:58:58 +01:00
|
|
|
next_scale_mark_pos = int(next_scale_mark * scale_minor_divisor / ms_per_pixel) - pixel_left;
|
2010-12-08 04:36:10 +01:00
|
|
|
bool mark_is_major = next_scale_mark % scale_major_modulo == 0;
|
2006-01-16 22:02:54 +01:00
|
|
|
|
2010-12-08 04:36:10 +01:00
|
|
|
if (mark_is_major)
|
|
|
|
dc.DrawLine(next_scale_mark_pos, bottom-6, next_scale_mark_pos, bottom-1);
|
|
|
|
else
|
|
|
|
dc.DrawLine(next_scale_mark_pos, bottom-4, next_scale_mark_pos, bottom-1);
|
2006-01-16 22:02:54 +01:00
|
|
|
|
2010-12-08 04:36:10 +01:00
|
|
|
// Print time labels on major scale marks
|
|
|
|
if (mark_is_major && next_scale_mark_pos > last_text_right)
|
|
|
|
{
|
2012-02-02 00:58:58 +01:00
|
|
|
double mark_time = next_scale_mark * scale_minor_divisor / 1000.0;
|
2010-12-08 04:36:10 +01:00
|
|
|
int mark_hour = (int)(mark_time / 3600);
|
|
|
|
int mark_minute = (int)(mark_time / 60) % 60;
|
2012-10-05 04:10:20 +02:00
|
|
|
double mark_second = mark_time - mark_hour*3600.0 - mark_minute*60.0;
|
2006-01-16 22:02:54 +01:00
|
|
|
|
2010-12-08 04:36:10 +01:00
|
|
|
wxString time_string;
|
|
|
|
bool changed_hour = mark_hour != last_hour;
|
|
|
|
bool changed_minute = mark_minute != last_minute;
|
2006-01-16 22:02:54 +01:00
|
|
|
|
2010-12-08 04:36:10 +01:00
|
|
|
if (changed_hour)
|
|
|
|
{
|
2014-05-29 17:28:37 +02:00
|
|
|
time_string = fmt_wx("%d:%02d:", mark_hour, mark_minute);
|
2010-12-08 04:36:10 +01:00
|
|
|
last_hour = mark_hour;
|
|
|
|
last_minute = mark_minute;
|
|
|
|
}
|
|
|
|
else if (changed_minute)
|
|
|
|
{
|
2014-05-29 17:28:37 +02:00
|
|
|
time_string = fmt_wx("%d:", mark_minute);
|
2010-12-08 04:36:10 +01:00
|
|
|
last_minute = mark_minute;
|
|
|
|
}
|
|
|
|
if (scale_minor >= Sc_Decisecond)
|
2014-05-29 17:28:37 +02:00
|
|
|
time_string += fmt_wx("%02d", mark_second);
|
2010-12-08 04:36:10 +01:00
|
|
|
else if (scale_minor == Sc_Centisecond)
|
2014-05-29 17:28:37 +02:00
|
|
|
time_string += fmt_wx("%02.1f", mark_second);
|
2010-12-08 04:36:10 +01:00
|
|
|
else
|
2014-05-29 17:28:37 +02:00
|
|
|
time_string += fmt_wx("%02.2f", mark_second);
|
2010-12-08 04:36:10 +01:00
|
|
|
|
|
|
|
int tw, th;
|
|
|
|
dc.GetTextExtent(time_string, &tw, &th);
|
|
|
|
last_text_right = next_scale_mark_pos + tw;
|
|
|
|
|
|
|
|
dc.DrawText(time_string, next_scale_mark_pos, 0);
|
|
|
|
}
|
2006-01-16 22:02:54 +01:00
|
|
|
|
2010-12-08 04:36:10 +01:00
|
|
|
next_scale_mark += 1;
|
|
|
|
|
|
|
|
} while (next_scale_mark_pos < bounds.width);
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2014-03-13 02:39:07 +01:00
|
|
|
class AudioStyleRangeMerger final : public AudioRenderingStyleRanges {
|
2012-02-02 00:58:58 +01:00
|
|
|
typedef std::map<int, AudioRenderingStyle> style_map;
|
2011-11-18 23:56:45 +01:00
|
|
|
public:
|
|
|
|
typedef style_map::iterator iterator;
|
|
|
|
|
|
|
|
private:
|
|
|
|
style_map points;
|
|
|
|
|
2012-02-02 00:58:58 +01:00
|
|
|
void Split(int point)
|
2011-11-18 23:56:45 +01:00
|
|
|
{
|
2013-11-21 18:13:36 +01:00
|
|
|
auto it = points.lower_bound(point);
|
2011-11-18 23:56:45 +01:00
|
|
|
if (it == points.end() || it->first != point)
|
|
|
|
{
|
|
|
|
assert(it != points.begin());
|
|
|
|
points[point] = (--it)->second;
|
|
|
|
}
|
|
|
|
}
|
2006-01-16 22:02:54 +01:00
|
|
|
|
2012-02-02 00:58:58 +01:00
|
|
|
void Restyle(int start, int end, AudioRenderingStyle style)
|
2011-11-18 23:56:45 +01:00
|
|
|
{
|
|
|
|
assert(points.lower_bound(end) != points.end());
|
2013-11-21 18:13:36 +01:00
|
|
|
for (auto pt = points.lower_bound(start); pt->first < end; ++pt)
|
2011-11-18 23:56:45 +01:00
|
|
|
{
|
|
|
|
if (style > pt->second)
|
|
|
|
pt->second = style;
|
|
|
|
}
|
|
|
|
}
|
2006-01-16 22:02:54 +01:00
|
|
|
|
2011-11-18 23:56:45 +01:00
|
|
|
public:
|
|
|
|
AudioStyleRangeMerger()
|
|
|
|
{
|
|
|
|
points[0] = AudioStyle_Normal;
|
|
|
|
}
|
|
|
|
|
2013-11-21 18:13:36 +01:00
|
|
|
void AddRange(int start, int end, AudioRenderingStyle style) override
|
2011-11-18 23:56:45 +01:00
|
|
|
{
|
|
|
|
|
|
|
|
if (start < 0) start = 0;
|
|
|
|
if (end < start) return;
|
|
|
|
|
|
|
|
Split(start);
|
|
|
|
Split(end);
|
|
|
|
Restyle(start, end, style);
|
|
|
|
}
|
|
|
|
|
|
|
|
iterator begin() { return points.begin(); }
|
|
|
|
iterator end() { return points.end(); }
|
|
|
|
};
|
2006-01-16 22:02:54 +01:00
|
|
|
|
2014-04-22 21:34:20 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
class AudioMarkerInteractionObject final : public AudioDisplayInteractionObject {
|
|
|
|
// Object-pair being interacted with
|
|
|
|
std::vector<AudioMarker*> markers;
|
|
|
|
AudioTimingController *timing_controller;
|
|
|
|
// Audio display drag is happening on
|
|
|
|
AudioDisplay *display;
|
|
|
|
// Mouse button used to initiate the drag
|
|
|
|
wxMouseButton button_used;
|
|
|
|
// Default to snapping to snappable markers
|
2014-05-12 18:30:14 +02:00
|
|
|
bool default_snap = OPT_GET("Audio/Snap/Enable")->GetBool();
|
2014-04-22 21:34:20 +02:00
|
|
|
// Range in pixels to snap at
|
2014-05-12 18:30:14 +02:00
|
|
|
int snap_range = OPT_GET("Audio/Snap/Distance")->GetInt();
|
2014-04-22 21:34:20 +02:00
|
|
|
|
|
|
|
public:
|
|
|
|
AudioMarkerInteractionObject(std::vector<AudioMarker*> markers, AudioTimingController *timing_controller, AudioDisplay *display, wxMouseButton button_used)
|
|
|
|
: markers(std::move(markers))
|
|
|
|
, timing_controller(timing_controller)
|
|
|
|
, display(display)
|
|
|
|
, button_used(button_used)
|
|
|
|
{
|
|
|
|
}
|
|
|
|
|
|
|
|
bool OnMouseEvent(wxMouseEvent &event) override
|
|
|
|
{
|
|
|
|
if (event.Dragging())
|
|
|
|
{
|
|
|
|
timing_controller->OnMarkerDrag(
|
|
|
|
markers,
|
|
|
|
display->TimeFromRelativeX(event.GetPosition().x),
|
|
|
|
default_snap != event.ShiftDown() ? display->TimeFromAbsoluteX(snap_range) : 0);
|
|
|
|
}
|
|
|
|
|
|
|
|
// We lose the marker drag if the button used to initiate it goes up
|
|
|
|
return !event.ButtonUp(button_used);
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Get the position in milliseconds of this group of markers
|
|
|
|
int GetPosition() const { return markers.front()->GetPosition(); }
|
|
|
|
};
|
|
|
|
|
2011-07-30 01:16:55 +02:00
|
|
|
AudioDisplay::AudioDisplay(wxWindow *parent, AudioController *controller, agi::Context *context)
|
2010-12-08 04:36:10 +01:00
|
|
|
: wxWindow(parent, -1, wxDefaultPosition, wxDefaultSize, wxWANTS_CHARS|wxBORDER_SIMPLE)
|
2014-05-22 01:23:28 +02:00
|
|
|
, audio_open_connection(context->project->AddAudioProviderListener(&AudioDisplay::OnAudioOpen, this))
|
2011-07-30 01:16:55 +02:00
|
|
|
, context(context)
|
2014-04-23 22:53:24 +02:00
|
|
|
, audio_renderer(agi::make_unique<AudioRenderer>())
|
2010-12-08 09:09:05 +01:00
|
|
|
, controller(controller)
|
2014-04-23 22:53:24 +02:00
|
|
|
, scrollbar(agi::make_unique<AudioDisplayScrollbar>(this))
|
|
|
|
, timeline(agi::make_unique<AudioDisplayTimeline>(this))
|
2014-07-16 20:09:15 +02:00
|
|
|
, style_ranges({{0, 0}})
|
2010-12-08 04:36:10 +01:00
|
|
|
{
|
|
|
|
audio_renderer->SetAmplitudeScale(scale_amplitude);
|
|
|
|
SetZoomLevel(0);
|
2006-01-16 22:02:54 +01:00
|
|
|
|
2010-12-08 04:36:10 +01:00
|
|
|
SetMinClientSize(wxSize(-1, 70));
|
|
|
|
SetBackgroundStyle(wxBG_STYLE_PAINT);
|
|
|
|
SetThemeEnabled(false);
|
2011-10-25 22:28:30 +02:00
|
|
|
|
|
|
|
Bind(wxEVT_LEFT_DOWN, &AudioDisplay::OnMouseEvent, this);
|
|
|
|
Bind(wxEVT_MIDDLE_DOWN, &AudioDisplay::OnMouseEvent, this);
|
|
|
|
Bind(wxEVT_RIGHT_DOWN, &AudioDisplay::OnMouseEvent, this);
|
|
|
|
Bind(wxEVT_LEFT_UP, &AudioDisplay::OnMouseEvent, this);
|
|
|
|
Bind(wxEVT_MIDDLE_UP, &AudioDisplay::OnMouseEvent, this);
|
|
|
|
Bind(wxEVT_RIGHT_UP, &AudioDisplay::OnMouseEvent, this);
|
|
|
|
Bind(wxEVT_MOTION, &AudioDisplay::OnMouseEvent, this);
|
2012-10-02 16:40:50 +02:00
|
|
|
Bind(wxEVT_ENTER_WINDOW, &AudioDisplay::OnMouseEnter, this);
|
|
|
|
Bind(wxEVT_LEAVE_WINDOW, &AudioDisplay::OnMouseLeave, this);
|
2012-10-02 16:25:29 +02:00
|
|
|
Bind(wxEVT_PAINT, &AudioDisplay::OnPaint, this);
|
|
|
|
Bind(wxEVT_SIZE, &AudioDisplay::OnSize, this);
|
|
|
|
Bind(wxEVT_KILL_FOCUS, &AudioDisplay::OnFocus, this);
|
|
|
|
Bind(wxEVT_SET_FOCUS, &AudioDisplay::OnFocus, this);
|
|
|
|
Bind(wxEVT_CHAR_HOOK, &AudioDisplay::OnKeyDown, this);
|
|
|
|
Bind(wxEVT_KEY_DOWN, &AudioDisplay::OnKeyDown, this);
|
2012-05-01 04:50:03 +02:00
|
|
|
scroll_timer.Bind(wxEVT_TIMER, &AudioDisplay::OnScrollTimer, this);
|
2014-04-22 21:34:20 +02:00
|
|
|
load_timer.Bind(wxEVT_TIMER, &AudioDisplay::OnLoadTimer, this);
|
2010-12-08 04:36:10 +01:00
|
|
|
}
|
2006-01-16 22:02:54 +01:00
|
|
|
|
2010-12-08 04:36:10 +01:00
|
|
|
AudioDisplay::~AudioDisplay()
|
|
|
|
{
|
2006-01-16 22:02:54 +01:00
|
|
|
}
|
|
|
|
|
2010-12-08 04:36:10 +01:00
|
|
|
void AudioDisplay::ScrollBy(int pixel_amount)
|
|
|
|
{
|
|
|
|
ScrollPixelToLeft(scroll_left + pixel_amount);
|
2006-01-16 22:02:54 +01:00
|
|
|
}
|
|
|
|
|
2010-12-08 04:36:10 +01:00
|
|
|
void AudioDisplay::ScrollPixelToLeft(int pixel_position)
|
|
|
|
{
|
|
|
|
const int client_width = GetClientRect().GetWidth();
|
|
|
|
|
|
|
|
if (pixel_position + client_width >= pixel_audio_width)
|
|
|
|
pixel_position = pixel_audio_width - client_width;
|
|
|
|
if (pixel_position < 0)
|
|
|
|
pixel_position = 0;
|
|
|
|
|
2011-09-15 07:16:18 +02:00
|
|
|
scroll_left = pixel_position;
|
|
|
|
scrollbar->SetPosition(scroll_left);
|
|
|
|
timeline->SetPosition(scroll_left);
|
|
|
|
Refresh();
|
2006-01-16 22:02:54 +01:00
|
|
|
}
|
|
|
|
|
2012-02-02 00:58:58 +01:00
|
|
|
void AudioDisplay::ScrollTimeRangeInView(const TimeRange &range)
|
2010-12-08 04:36:10 +01:00
|
|
|
{
|
|
|
|
int client_width = GetClientRect().GetWidth();
|
2012-02-02 00:58:58 +01:00
|
|
|
int range_begin = AbsoluteXFromTime(range.begin());
|
|
|
|
int range_end = AbsoluteXFromTime(range.end());
|
2010-12-08 04:36:10 +01:00
|
|
|
int range_len = range_end - range_begin;
|
|
|
|
|
2012-10-06 16:23:49 +02:00
|
|
|
// Remove 5 % from each side of the client area.
|
2010-12-08 04:36:10 +01:00
|
|
|
int leftadjust = client_width / 20;
|
2012-10-06 16:23:49 +02:00
|
|
|
int client_left = scroll_left + leftadjust;
|
2010-12-08 04:36:10 +01:00
|
|
|
client_width = client_width * 9 / 10;
|
2006-01-16 22:02:54 +01:00
|
|
|
|
2012-10-06 16:23:49 +02:00
|
|
|
// Is everything already in view?
|
|
|
|
if (range_begin >= client_left && range_end <= client_left+client_width)
|
|
|
|
return;
|
|
|
|
|
2010-12-08 04:36:10 +01:00
|
|
|
// The entire range can fit inside the view, center it
|
|
|
|
if (range_len < client_width)
|
|
|
|
{
|
|
|
|
ScrollPixelToLeft(range_begin - (client_width-range_len)/2 - leftadjust);
|
|
|
|
}
|
2006-01-16 22:02:54 +01:00
|
|
|
|
2010-12-08 04:36:10 +01:00
|
|
|
// Range doesn't fit in view and we're viewing a middle part of it, just leave it alone
|
2012-10-06 16:23:49 +02:00
|
|
|
else if (range_begin < client_left && range_end > client_left+client_width)
|
2010-12-08 04:36:10 +01:00
|
|
|
{
|
|
|
|
// nothing
|
2006-01-16 22:02:54 +01:00
|
|
|
}
|
|
|
|
|
2010-12-08 04:36:10 +01:00
|
|
|
// Right edge is in view, scroll it as far to the right as possible
|
2012-10-06 16:23:49 +02:00
|
|
|
else if (range_end >= client_left && range_end < client_left+client_width)
|
2010-12-08 04:36:10 +01:00
|
|
|
{
|
|
|
|
ScrollPixelToLeft(range_end - client_width - leftadjust);
|
|
|
|
}
|
2006-01-16 22:02:54 +01:00
|
|
|
|
2010-12-08 04:36:10 +01:00
|
|
|
// Nothing is in view or the left edge is in view, scroll left edge as far to the left as possible
|
|
|
|
else
|
|
|
|
{
|
|
|
|
ScrollPixelToLeft(range_begin - leftadjust);
|
2006-01-16 22:02:54 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2010-12-08 04:36:10 +01:00
|
|
|
void AudioDisplay::SetZoomLevel(int new_zoom_level)
|
|
|
|
{
|
|
|
|
zoom_level = new_zoom_level;
|
2006-01-16 22:02:54 +01:00
|
|
|
|
2010-12-08 04:36:10 +01:00
|
|
|
const int factor = GetZoomLevelFactor(zoom_level);
|
2012-02-02 00:58:58 +01:00
|
|
|
const int base_pixels_per_second = 50; /// @todo Make this customisable
|
|
|
|
const double base_ms_per_pixel = 1000.0 / base_pixels_per_second;
|
|
|
|
const double new_ms_per_pixel = 100.0 * base_ms_per_pixel / factor;
|
2006-01-16 22:02:54 +01:00
|
|
|
|
2013-08-20 21:11:39 +02:00
|
|
|
if (ms_per_pixel == new_ms_per_pixel) return;
|
2010-12-08 09:09:37 +01:00
|
|
|
|
2013-08-20 21:11:39 +02:00
|
|
|
int client_width = GetClientSize().GetWidth();
|
|
|
|
double cursor_pos = track_cursor_pos >= 0 ? track_cursor_pos - scroll_left : client_width / 2.0;
|
|
|
|
double cursor_time = (scroll_left + cursor_pos) * ms_per_pixel;
|
2006-02-20 23:32:20 +01:00
|
|
|
|
2013-08-20 21:11:39 +02:00
|
|
|
ms_per_pixel = new_ms_per_pixel;
|
2014-05-26 18:14:51 +02:00
|
|
|
pixel_audio_width = std::max(1, int(GetDuration() / ms_per_pixel));
|
2010-12-08 09:09:37 +01:00
|
|
|
|
2013-08-20 21:11:39 +02:00
|
|
|
audio_renderer->SetMillisecondsPerPixel(ms_per_pixel);
|
|
|
|
scrollbar->ChangeLengths(pixel_audio_width, client_width);
|
|
|
|
timeline->ChangeZoom(ms_per_pixel);
|
|
|
|
|
|
|
|
ScrollPixelToLeft(AbsoluteXFromTime(cursor_time) - cursor_pos);
|
2013-08-20 21:14:21 +02:00
|
|
|
if (track_cursor_pos >= 0)
|
|
|
|
track_cursor_pos = AbsoluteXFromTime(cursor_time);
|
2013-08-20 21:11:39 +02:00
|
|
|
Refresh();
|
2006-01-16 22:02:54 +01:00
|
|
|
}
|
|
|
|
|
2010-12-08 04:36:10 +01:00
|
|
|
wxString AudioDisplay::GetZoomLevelDescription(int level) const
|
|
|
|
{
|
|
|
|
const int factor = GetZoomLevelFactor(level);
|
|
|
|
const int base_pixels_per_second = 50; /// @todo Make this customisable along with the above
|
|
|
|
const int second_pixels = 100 * base_pixels_per_second / factor;
|
2006-01-16 22:02:54 +01:00
|
|
|
|
2014-05-29 17:28:37 +02:00
|
|
|
return fmt_tl("%d%%, %d pixel/second", factor, second_pixels);
|
2006-01-16 22:02:54 +01:00
|
|
|
}
|
|
|
|
|
2010-12-08 04:36:10 +01:00
|
|
|
int AudioDisplay::GetZoomLevelFactor(int level)
|
|
|
|
{
|
|
|
|
int factor = 100;
|
|
|
|
|
|
|
|
if (level > 0)
|
|
|
|
{
|
|
|
|
factor += 25 * level;
|
|
|
|
}
|
|
|
|
else if (level < 0)
|
|
|
|
{
|
|
|
|
if (level >= -5)
|
|
|
|
factor += 10 * level;
|
|
|
|
else if (level >= -11)
|
|
|
|
factor = 50 + (level+5) * 5;
|
|
|
|
else
|
|
|
|
factor = 20 + level + 11;
|
|
|
|
if (factor <= 0)
|
|
|
|
factor = 1;
|
2009-06-17 01:44:06 +02:00
|
|
|
}
|
|
|
|
|
2010-12-08 04:36:10 +01:00
|
|
|
return factor;
|
|
|
|
}
|
2006-01-16 22:02:54 +01:00
|
|
|
|
2010-12-08 04:36:10 +01:00
|
|
|
void AudioDisplay::SetAmplitudeScale(float scale)
|
|
|
|
{
|
|
|
|
audio_renderer->SetAmplitudeScale(scale);
|
|
|
|
Refresh();
|
|
|
|
}
|
|
|
|
|
|
|
|
void AudioDisplay::ReloadRenderingSettings()
|
|
|
|
{
|
2011-11-30 22:04:37 +01:00
|
|
|
std::string colour_scheme_name;
|
|
|
|
|
2011-09-15 07:16:18 +02:00
|
|
|
if (OPT_GET("Audio/Spectrum")->GetBool())
|
|
|
|
{
|
2011-11-30 22:04:37 +01:00
|
|
|
colour_scheme_name = OPT_GET("Colour/Audio Display/Spectrum")->GetString();
|
2014-04-23 22:53:24 +02:00
|
|
|
auto audio_spectrum_renderer = agi::make_unique<AudioSpectrumRenderer>(colour_scheme_name);
|
2011-09-15 07:16:18 +02:00
|
|
|
|
|
|
|
int64_t spectrum_quality = OPT_GET("Audio/Renderer/Spectrum/Quality")->GetInt();
|
2011-12-22 22:25:49 +01:00
|
|
|
#ifdef WITH_FFTW3
|
2011-09-15 07:16:18 +02:00
|
|
|
// FFTW is so fast we can afford to upgrade quality by two levels
|
|
|
|
spectrum_quality += 2;
|
2007-09-21 23:00:41 +02:00
|
|
|
#endif
|
2011-11-07 05:14:09 +01:00
|
|
|
spectrum_quality = mid<int64_t>(0, spectrum_quality, 5);
|
2006-01-16 22:02:54 +01:00
|
|
|
|
2011-09-15 07:16:18 +02:00
|
|
|
// Quality indexes: 0 1 2 3 4 5
|
|
|
|
int spectrum_width[] = {8, 9, 9, 9, 10, 11};
|
|
|
|
int spectrum_distance[] = {8, 8, 7, 6, 6, 5};
|
2006-01-16 22:02:54 +01:00
|
|
|
|
2011-09-15 07:16:18 +02:00
|
|
|
audio_spectrum_renderer->SetResolution(
|
|
|
|
spectrum_width[spectrum_quality],
|
|
|
|
spectrum_distance[spectrum_quality]);
|
2006-01-16 22:02:54 +01:00
|
|
|
|
2013-06-08 06:19:40 +02:00
|
|
|
audio_renderer_provider = std::move(audio_spectrum_renderer);
|
2011-09-15 07:16:18 +02:00
|
|
|
}
|
2010-12-08 04:36:10 +01:00
|
|
|
else
|
2011-09-15 07:16:18 +02:00
|
|
|
{
|
2011-11-30 22:04:37 +01:00
|
|
|
colour_scheme_name = OPT_GET("Colour/Audio Display/Waveform")->GetString();
|
2014-04-23 22:53:24 +02:00
|
|
|
audio_renderer_provider = agi::make_unique<AudioWaveformRenderer>(colour_scheme_name);
|
2011-09-15 07:16:18 +02:00
|
|
|
}
|
2006-03-07 01:12:50 +01:00
|
|
|
|
2011-09-15 07:16:18 +02:00
|
|
|
audio_renderer->SetRenderer(audio_renderer_provider.get());
|
2011-11-30 22:04:37 +01:00
|
|
|
scrollbar->SetColourScheme(colour_scheme_name);
|
2011-11-30 22:04:46 +01:00
|
|
|
timeline->SetColourScheme(colour_scheme_name);
|
2006-03-06 01:45:24 +01:00
|
|
|
|
2010-12-08 04:36:10 +01:00
|
|
|
Refresh();
|
|
|
|
}
|
2006-01-16 22:02:54 +01:00
|
|
|
|
2014-04-22 21:34:20 +02:00
|
|
|
void AudioDisplay::OnLoadTimer(wxTimerEvent&)
|
|
|
|
{
|
|
|
|
using namespace std::chrono;
|
|
|
|
if (provider)
|
|
|
|
{
|
|
|
|
const auto now = steady_clock::now();
|
|
|
|
const auto elapsed = duration_cast<milliseconds>(now - audio_load_start_time).count();
|
|
|
|
if (elapsed == 0) return;
|
|
|
|
|
|
|
|
const int64_t new_decoded_count = provider->GetDecodedSamples();
|
|
|
|
if (new_decoded_count != last_sample_decoded)
|
|
|
|
audio_load_speed = (audio_load_speed + (double)new_decoded_count / elapsed) / 2;
|
|
|
|
if (audio_load_speed == 0) return;
|
|
|
|
|
|
|
|
int new_pos = AbsoluteXFromTime(elapsed * audio_load_speed * 1000.0 / provider->GetSampleRate());
|
|
|
|
if (new_pos > audio_load_position)
|
|
|
|
audio_load_position = new_pos;
|
|
|
|
|
|
|
|
const double left = last_sample_decoded * 1000.0 / provider->GetSampleRate() / ms_per_pixel;
|
|
|
|
const double right = new_decoded_count * 1000.0 / provider->GetSampleRate() / ms_per_pixel;
|
|
|
|
|
|
|
|
if (left < scroll_left + pixel_audio_width && right >= scroll_left)
|
|
|
|
Refresh();
|
|
|
|
else
|
|
|
|
RefreshRect(scrollbar->GetBounds());
|
|
|
|
last_sample_decoded = new_decoded_count;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!provider || last_sample_decoded == provider->GetNumSamples()) {
|
|
|
|
load_timer.Stop();
|
|
|
|
audio_load_position = -1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2011-12-22 22:09:31 +01:00
|
|
|
void AudioDisplay::OnPaint(wxPaintEvent&)
|
2010-12-08 04:36:10 +01:00
|
|
|
{
|
2014-04-22 21:34:20 +02:00
|
|
|
if (!audio_renderer_provider || !provider) return;
|
2012-05-13 02:58:06 +02:00
|
|
|
|
2010-12-08 04:36:10 +01:00
|
|
|
wxAutoBufferedPaintDC dc(this);
|
|
|
|
|
2012-01-08 02:36:58 +01:00
|
|
|
wxRect audio_bounds(0, audio_top, GetClientSize().GetWidth(), audio_height);
|
2010-12-08 04:36:10 +01:00
|
|
|
bool redraw_scrollbar = false;
|
|
|
|
bool redraw_timeline = false;
|
|
|
|
|
2011-09-15 07:16:26 +02:00
|
|
|
for (wxRegionIterator region(GetUpdateRegion()); region; ++region)
|
2010-12-08 04:36:10 +01:00
|
|
|
{
|
|
|
|
wxRect updrect = region.GetRect();
|
|
|
|
|
2012-01-08 02:36:58 +01:00
|
|
|
redraw_scrollbar |= scrollbar->GetBounds().Intersects(updrect);
|
|
|
|
redraw_timeline |= timeline->GetBounds().Intersects(updrect);
|
2010-12-08 04:36:10 +01:00
|
|
|
|
2012-01-08 02:36:58 +01:00
|
|
|
if (audio_bounds.Intersects(updrect))
|
2010-12-08 04:36:10 +01:00
|
|
|
{
|
2012-02-02 00:58:58 +01:00
|
|
|
TimeRange updtime(
|
|
|
|
std::max(0, TimeFromRelativeX(updrect.x - foot_size)),
|
|
|
|
std::max(0, TimeFromRelativeX(updrect.x + updrect.width + foot_size)));
|
2012-01-08 02:36:58 +01:00
|
|
|
|
2012-02-02 00:58:58 +01:00
|
|
|
PaintAudio(dc, updtime, updrect);
|
|
|
|
PaintMarkers(dc, updtime);
|
|
|
|
PaintLabels(dc, updtime);
|
2011-09-15 07:16:26 +02:00
|
|
|
}
|
2012-01-08 02:36:58 +01:00
|
|
|
}
|
2011-09-15 07:16:26 +02:00
|
|
|
|
2012-01-08 02:36:58 +01:00
|
|
|
if (track_cursor_pos >= 0)
|
|
|
|
PaintTrackCursor(dc);
|
2011-09-15 07:16:26 +02:00
|
|
|
|
2012-01-08 02:36:58 +01:00
|
|
|
if (redraw_scrollbar)
|
2014-04-22 21:34:20 +02:00
|
|
|
scrollbar->Paint(dc, HasFocus(), audio_load_position);
|
2012-01-08 02:36:58 +01:00
|
|
|
if (redraw_timeline)
|
|
|
|
timeline->Paint(dc);
|
|
|
|
}
|
2011-11-18 23:56:45 +01:00
|
|
|
|
2014-12-20 20:09:15 +01:00
|
|
|
void AudioDisplay::PaintAudio(wxDC &dc, const TimeRange updtime, const wxRect updrect)
|
2012-01-08 02:36:58 +01:00
|
|
|
{
|
2014-07-16 20:09:15 +02:00
|
|
|
auto pt = begin(style_ranges), pe = end(style_ranges);
|
|
|
|
while (pt != pe && pt + 1 != pe && (pt + 1)->first < updtime.begin()) ++pt;
|
2011-11-18 23:56:45 +01:00
|
|
|
|
2014-07-16 20:09:15 +02:00
|
|
|
while (pt != pe && pt->first < updtime.end())
|
2012-01-08 02:36:58 +01:00
|
|
|
{
|
2014-12-20 20:09:15 +01:00
|
|
|
const auto range_style = static_cast<AudioRenderingStyle>(pt->second);
|
|
|
|
const int range_x1 = std::max(updrect.x, RelativeXFromTime(pt->first));
|
2014-12-21 20:28:42 +01:00
|
|
|
int range_x2 = updrect.x + updrect.width;
|
|
|
|
if (++pt != pe)
|
|
|
|
range_x2 = std::min(range_x2, RelativeXFromTime(pt->first));
|
2011-11-18 23:56:45 +01:00
|
|
|
|
2012-01-08 02:36:58 +01:00
|
|
|
if (range_x2 > range_x1)
|
2014-12-20 20:09:15 +01:00
|
|
|
audio_renderer->Render(dc, wxPoint(range_x1, audio_top),
|
|
|
|
range_x1 + scroll_left, range_x2 - range_x1, range_style);
|
2012-01-08 02:36:58 +01:00
|
|
|
}
|
|
|
|
}
|
2006-01-16 22:02:54 +01:00
|
|
|
|
2012-02-02 00:58:58 +01:00
|
|
|
void AudioDisplay::PaintMarkers(wxDC &dc, TimeRange updtime)
|
2012-01-08 02:36:58 +01:00
|
|
|
{
|
|
|
|
AudioMarkerVector markers;
|
2012-02-02 00:59:12 +01:00
|
|
|
controller->GetTimingController()->GetMarkers(updtime, markers);
|
2012-01-08 02:36:58 +01:00
|
|
|
if (markers.empty()) return;
|
2011-09-15 07:16:26 +02:00
|
|
|
|
2012-01-08 02:36:58 +01:00
|
|
|
wxDCPenChanger pen_retainer(dc, wxPen());
|
|
|
|
wxDCBrushChanger brush_retainer(dc, wxBrush());
|
2012-11-04 04:53:03 +01:00
|
|
|
for (const auto marker : markers)
|
2012-01-08 02:36:58 +01:00
|
|
|
{
|
2012-02-02 00:58:58 +01:00
|
|
|
int marker_x = RelativeXFromTime(marker->GetPosition());
|
2012-01-08 02:36:58 +01:00
|
|
|
|
|
|
|
dc.SetPen(marker->GetStyle());
|
|
|
|
dc.DrawLine(marker_x, audio_top, marker_x, audio_top+audio_height);
|
|
|
|
|
|
|
|
if (marker->GetFeet() == AudioMarker::Feet_None) continue;
|
|
|
|
|
|
|
|
dc.SetBrush(wxBrush(marker->GetStyle().GetColour()));
|
|
|
|
dc.SetPen(*wxTRANSPARENT_PEN);
|
|
|
|
|
|
|
|
if (marker->GetFeet() & AudioMarker::Feet_Left)
|
|
|
|
PaintFoot(dc, marker_x, -1);
|
|
|
|
if (marker->GetFeet() & AudioMarker::Feet_Right)
|
|
|
|
PaintFoot(dc, marker_x, 1);
|
2010-12-08 04:36:10 +01:00
|
|
|
}
|
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()) {
|
|
|
|
dc.SetBrush(wxBrush(*wxGREEN));
|
|
|
|
dc.SetPen(*wxTRANSPARENT_PEN);
|
|
|
|
int marker_x = RelativeXFromTime(controller->GetTimingController()->GetTapMarkerPosition());
|
|
|
|
PaintTapMarker(dc, marker_x);
|
|
|
|
}
|
2012-01-08 02:36:58 +01:00
|
|
|
}
|
2010-12-08 04:36:10 +01:00
|
|
|
|
2012-01-08 02:36:58 +01:00
|
|
|
void AudioDisplay::PaintFoot(wxDC &dc, int marker_x, int dir)
|
|
|
|
{
|
|
|
|
wxPoint foot_top[3] = { wxPoint(foot_size * dir, 0), wxPoint(0, 0), wxPoint(0, foot_size) };
|
|
|
|
wxPoint foot_bot[3] = { wxPoint(foot_size * dir, 0), wxPoint(0, -foot_size), wxPoint(0, 0) };
|
|
|
|
dc.DrawPolygon(3, foot_top, marker_x, audio_top);
|
|
|
|
dc.DrawPolygon(3, foot_bot, marker_x, audio_top+audio_height);
|
|
|
|
}
|
|
|
|
|
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 AudioDisplay::PaintTapMarker(wxDC &dc, int marker_x)
|
|
|
|
{
|
|
|
|
wxPoint arrow[3] = { wxPoint(-foot_size * 2, 0), wxPoint(0, -foot_size * 2), wxPoint(foot_size * 2, 0) };
|
|
|
|
dc.DrawPolygon(3, arrow, marker_x, audio_top+audio_height);
|
|
|
|
}
|
|
|
|
|
2012-02-02 00:58:58 +01:00
|
|
|
void AudioDisplay::PaintLabels(wxDC &dc, TimeRange updtime)
|
2012-01-08 02:36:58 +01:00
|
|
|
{
|
|
|
|
std::vector<AudioLabelProvider::AudioLabel> labels;
|
2012-02-02 00:59:12 +01:00
|
|
|
controller->GetTimingController()->GetLabels(updtime, labels);
|
2012-01-08 02:36:58 +01:00
|
|
|
if (labels.empty()) return;
|
|
|
|
|
|
|
|
wxDCFontChanger fc(dc);
|
|
|
|
wxFont font = dc.GetFont();
|
|
|
|
font.SetWeight(wxFONTWEIGHT_BOLD);
|
2014-07-10 20:31:10 +02:00
|
|
|
fc.Set(font);
|
2012-01-08 02:36:58 +01:00
|
|
|
dc.SetTextForeground(*wxWHITE);
|
2012-11-04 04:53:03 +01:00
|
|
|
for (auto const& label : labels)
|
2010-12-08 04:36:10 +01:00
|
|
|
{
|
2012-11-04 04:53:03 +01:00
|
|
|
wxSize extent = dc.GetTextExtent(label.text);
|
|
|
|
int left = RelativeXFromTime(label.range.begin());
|
|
|
|
int width = AbsoluteXFromTime(label.range.length());
|
2010-12-08 04:36:10 +01:00
|
|
|
|
2012-01-08 02:36:58 +01:00
|
|
|
// If it doesn't fit, truncate
|
|
|
|
if (width < extent.GetWidth())
|
|
|
|
{
|
|
|
|
dc.SetClippingRegion(left, audio_top + 4, width, extent.GetHeight());
|
2012-11-04 04:53:03 +01:00
|
|
|
dc.DrawText(label.text, left, audio_top + 4);
|
2012-01-08 02:36:58 +01:00
|
|
|
dc.DestroyClippingRegion();
|
|
|
|
}
|
|
|
|
// Otherwise center in the range
|
|
|
|
else
|
2010-12-08 04:36:10 +01:00
|
|
|
{
|
2012-11-04 04:53:03 +01:00
|
|
|
dc.DrawText(label.text, left + (width - extent.GetWidth()) / 2, audio_top + 4);
|
2010-12-08 04:36:10 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2006-01-16 22:02:54 +01:00
|
|
|
|
2012-01-08 02:36:58 +01:00
|
|
|
void AudioDisplay::PaintTrackCursor(wxDC &dc) {
|
|
|
|
wxDCPenChanger penchanger(dc, wxPen(*wxWHITE));
|
|
|
|
dc.DrawLine(track_cursor_pos-scroll_left, audio_top, track_cursor_pos-scroll_left, audio_top+audio_height);
|
|
|
|
|
|
|
|
if (track_cursor_label.empty()) return;
|
|
|
|
|
|
|
|
wxDCFontChanger fc(dc);
|
|
|
|
wxFont font = dc.GetFont();
|
2013-12-10 18:18:27 +01:00
|
|
|
wxString face_name = FontFace("Audio/Track Cursor");
|
|
|
|
if (!face_name.empty())
|
|
|
|
font.SetFaceName(face_name);
|
2012-01-08 02:36:58 +01:00
|
|
|
font.SetWeight(wxFONTWEIGHT_BOLD);
|
2014-07-10 20:31:10 +02:00
|
|
|
fc.Set(font);
|
2012-01-08 02:36:58 +01:00
|
|
|
|
|
|
|
wxSize label_size(dc.GetTextExtent(track_cursor_label));
|
|
|
|
wxPoint label_pos(track_cursor_pos - scroll_left - label_size.x/2, audio_top + 2);
|
|
|
|
label_pos.x = mid(2, label_pos.x, GetClientSize().GetWidth() - label_size.x - 2);
|
|
|
|
|
|
|
|
int old_bg_mode = dc.GetBackgroundMode();
|
|
|
|
dc.SetBackgroundMode(wxTRANSPARENT);
|
|
|
|
|
|
|
|
// Draw border
|
|
|
|
dc.SetTextForeground(wxColour(64, 64, 64));
|
|
|
|
dc.DrawText(track_cursor_label, label_pos.x+1, label_pos.y+1);
|
|
|
|
dc.DrawText(track_cursor_label, label_pos.x+1, label_pos.y-1);
|
|
|
|
dc.DrawText(track_cursor_label, label_pos.x-1, label_pos.y+1);
|
|
|
|
dc.DrawText(track_cursor_label, label_pos.x-1, label_pos.y-1);
|
|
|
|
|
|
|
|
// Draw fill
|
|
|
|
dc.SetTextForeground(*wxWHITE);
|
|
|
|
dc.DrawText(track_cursor_label, label_pos.x, label_pos.y);
|
|
|
|
dc.SetBackgroundMode(old_bg_mode);
|
|
|
|
|
|
|
|
label_pos.x -= 2;
|
|
|
|
label_pos.y -= 2;
|
|
|
|
label_size.IncBy(4, 4);
|
|
|
|
// If the rendered text changes size we have to draw it an extra time to make sure the entire thing was drawn
|
|
|
|
bool need_extra_redraw = track_cursor_label_rect.GetSize() != label_size;
|
|
|
|
track_cursor_label_rect.SetPosition(label_pos);
|
|
|
|
track_cursor_label_rect.SetSize(label_size);
|
|
|
|
if (need_extra_redraw)
|
|
|
|
RefreshRect(track_cursor_label_rect, false);
|
|
|
|
}
|
2006-01-16 22:02:54 +01:00
|
|
|
|
2010-12-08 04:36:10 +01:00
|
|
|
void AudioDisplay::SetDraggedObject(AudioDisplayInteractionObject *new_obj)
|
|
|
|
{
|
|
|
|
dragged_object = new_obj;
|
2006-01-16 22:02:54 +01:00
|
|
|
|
2010-12-08 04:36:10 +01:00
|
|
|
if (dragged_object && !HasCapture())
|
|
|
|
CaptureMouse();
|
|
|
|
else if (!dragged_object && HasCapture())
|
|
|
|
ReleaseMouse();
|
2012-03-12 01:07:27 +01:00
|
|
|
|
|
|
|
if (!dragged_object)
|
|
|
|
audio_marker.reset();
|
2010-12-08 04:36:10 +01:00
|
|
|
}
|
2007-01-08 04:05:26 +01:00
|
|
|
|
2010-12-08 04:36:10 +01:00
|
|
|
void AudioDisplay::SetTrackCursor(int new_pos, bool show_time)
|
|
|
|
{
|
2013-08-20 21:11:39 +02:00
|
|
|
if (new_pos == track_cursor_pos) return;
|
2007-01-06 00:43:24 +01:00
|
|
|
|
2013-08-20 21:11:39 +02:00
|
|
|
int old_pos = track_cursor_pos;
|
|
|
|
track_cursor_pos = new_pos;
|
2010-12-08 04:36:10 +01:00
|
|
|
|
2016-04-09 02:51:14 +02:00
|
|
|
RefreshRect(wxRect(old_pos - scroll_left - 1, audio_top, 2, audio_height - 1), false);
|
|
|
|
RefreshRect(wxRect(new_pos - scroll_left - 1, audio_top, 2, audio_height - 1), false);
|
2010-12-08 04:36:10 +01:00
|
|
|
|
2013-08-20 21:11:39 +02:00
|
|
|
// Make sure the old label gets cleared away
|
|
|
|
RefreshRect(track_cursor_label_rect, false);
|
|
|
|
|
|
|
|
if (show_time)
|
|
|
|
{
|
2014-07-06 16:28:58 +02:00
|
|
|
agi::Time new_label_time = TimeFromAbsoluteX(track_cursor_pos);
|
|
|
|
track_cursor_label = to_wx(new_label_time.GetAssFormatted());
|
2013-08-20 21:11:39 +02:00
|
|
|
track_cursor_label_rect.x += new_pos - old_pos;
|
|
|
|
RefreshRect(track_cursor_label_rect, false);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
track_cursor_label_rect.SetSize(wxSize(0,0));
|
|
|
|
track_cursor_label.Clear();
|
2006-01-16 22:02:54 +01:00
|
|
|
}
|
2010-12-08 04:36:10 +01:00
|
|
|
}
|
2006-01-16 22:02:54 +01:00
|
|
|
|
2010-12-08 04:36:10 +01:00
|
|
|
void AudioDisplay::RemoveTrackCursor()
|
|
|
|
{
|
|
|
|
SetTrackCursor(-1, false);
|
2006-01-16 22:02:54 +01:00
|
|
|
}
|
|
|
|
|
2012-10-02 16:40:50 +02:00
|
|
|
void AudioDisplay::OnMouseEnter(wxMouseEvent&)
|
|
|
|
{
|
|
|
|
if (OPT_GET("Audio/Auto/Focus")->GetBool())
|
|
|
|
SetFocus();
|
|
|
|
}
|
|
|
|
|
|
|
|
void AudioDisplay::OnMouseLeave(wxMouseEvent&)
|
|
|
|
{
|
|
|
|
if (!controller->IsPlaying())
|
|
|
|
RemoveTrackCursor();
|
|
|
|
}
|
|
|
|
|
2010-12-08 04:36:10 +01:00
|
|
|
void AudioDisplay::OnMouseEvent(wxMouseEvent& event)
|
|
|
|
{
|
|
|
|
// If we have focus, we get mouse move events on Mac even when the mouse is
|
|
|
|
// outside our client rectangle, we don't want those.
|
|
|
|
if (event.Moving() && !GetClientRect().Contains(event.GetPosition()))
|
|
|
|
{
|
|
|
|
event.Skip();
|
2007-06-23 04:43:42 +02:00
|
|
|
return;
|
2006-02-25 07:04:46 +01:00
|
|
|
}
|
2006-03-06 00:01:02 +01:00
|
|
|
|
2012-10-02 16:40:50 +02:00
|
|
|
if (event.IsButton())
|
2010-12-08 04:36:10 +01:00
|
|
|
SetFocus();
|
|
|
|
|
2012-10-02 17:09:33 +02:00
|
|
|
const int mouse_x = event.GetPosition().x;
|
|
|
|
|
|
|
|
// Scroll the display after a mouse-up near one of the edges
|
|
|
|
if ((event.LeftUp() || event.RightUp()) && OPT_GET("Audio/Auto/Scroll")->GetBool())
|
|
|
|
{
|
|
|
|
const int width = GetClientSize().GetWidth();
|
|
|
|
if (mouse_x < width / 20) {
|
|
|
|
ScrollBy(-width / 3);
|
|
|
|
}
|
|
|
|
else if (width - mouse_x < width / 20) {
|
|
|
|
ScrollBy(width / 3);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2012-10-02 16:40:50 +02:00
|
|
|
if (ForwardMouseEvent(event))
|
2010-12-08 04:36:10 +01:00
|
|
|
return;
|
2006-01-16 22:02:54 +01:00
|
|
|
|
2011-12-28 22:26:54 +01:00
|
|
|
if (event.MiddleIsDown())
|
|
|
|
{
|
2012-10-02 16:40:50 +02:00
|
|
|
context->videoController->JumpToTime(TimeFromRelativeX(mouse_x), agi::vfr::EXACT);
|
2011-12-28 22:26:54 +01:00
|
|
|
return;
|
2006-01-16 22:02:54 +01:00
|
|
|
}
|
|
|
|
|
2011-12-28 22:26:54 +01:00
|
|
|
if (event.Moving() && !controller->IsPlaying())
|
2010-12-08 04:36:10 +01:00
|
|
|
{
|
2012-10-02 16:40:50 +02:00
|
|
|
SetTrackCursor(scroll_left + mouse_x, OPT_GET("Audio/Display/Draw/Cursor Time")->GetBool());
|
2007-01-06 05:27:09 +01:00
|
|
|
}
|
|
|
|
|
2011-12-28 22:26:54 +01:00
|
|
|
AudioTimingController *timing = controller->GetTimingController();
|
|
|
|
if (!timing) return;
|
2012-10-02 16:40:50 +02:00
|
|
|
const int drag_sensitivity = int(OPT_GET("Audio/Start Drag Sensitivity")->GetInt() * ms_per_pixel);
|
|
|
|
const int snap_sensitivity = OPT_GET("Audio/Snap/Enable")->GetBool() != event.ShiftDown() ? int(OPT_GET("Audio/Snap/Distance")->GetInt() * ms_per_pixel) : 0;
|
2011-12-28 22:26:54 +01:00
|
|
|
|
|
|
|
// Not scrollbar, not timeline, no button action
|
|
|
|
if (event.Moving())
|
2010-12-08 04:36:10 +01:00
|
|
|
{
|
2012-10-02 16:40:50 +02:00
|
|
|
const int timepos = TimeFromRelativeX(mouse_x);
|
2007-01-06 05:27:09 +01:00
|
|
|
|
2014-05-14 15:40:01 +02:00
|
|
|
if (timing->IsNearbyMarker(timepos, drag_sensitivity, event.AltDown()))
|
2011-12-28 22:26:54 +01:00
|
|
|
SetCursor(wxCursor(wxCURSOR_SIZEWE));
|
|
|
|
else
|
|
|
|
SetCursor(wxNullCursor);
|
2012-10-02 16:40:50 +02:00
|
|
|
return;
|
2006-01-16 22:02:54 +01:00
|
|
|
}
|
|
|
|
|
2012-10-02 16:40:50 +02:00
|
|
|
const int old_scroll_pos = scroll_left;
|
2011-12-30 16:52:04 +01:00
|
|
|
if (event.LeftDown() || event.RightDown())
|
2010-12-08 04:36:10 +01:00
|
|
|
{
|
2012-10-02 16:40:50 +02:00
|
|
|
const int timepos = TimeFromRelativeX(mouse_x);
|
2014-05-12 21:07:46 +02:00
|
|
|
std::vector<AudioMarker*> markers = event.LeftDown()
|
|
|
|
? timing->OnLeftClick(timepos, event.CmdDown(), event.AltDown(), drag_sensitivity, snap_sensitivity)
|
|
|
|
: timing->OnRightClick(timepos, event.CmdDown(), drag_sensitivity, snap_sensitivity);
|
2006-01-16 22:02:54 +01:00
|
|
|
|
2012-03-07 23:41:03 +01:00
|
|
|
// Clicking should never result in the audio display scrolling
|
|
|
|
ScrollPixelToLeft(old_scroll_pos);
|
|
|
|
|
2012-02-10 01:04:13 +01:00
|
|
|
if (markers.size())
|
2010-12-08 04:36:10 +01:00
|
|
|
{
|
|
|
|
RemoveTrackCursor();
|
2014-04-23 22:53:24 +02:00
|
|
|
audio_marker = agi::make_unique<AudioMarkerInteractionObject>(markers, timing, this, (wxMouseButton)event.GetButton());
|
2011-12-28 22:26:48 +01:00
|
|
|
SetDraggedObject(audio_marker.get());
|
2010-12-08 04:36:10 +01:00
|
|
|
return;
|
2006-01-16 22:02:54 +01:00
|
|
|
}
|
|
|
|
}
|
2010-12-08 04:36:10 +01:00
|
|
|
}
|
2006-01-16 22:02:54 +01:00
|
|
|
|
2012-10-02 16:40:50 +02:00
|
|
|
bool AudioDisplay::ForwardMouseEvent(wxMouseEvent &event) {
|
|
|
|
// Handle any ongoing drag
|
|
|
|
if (dragged_object && HasCapture())
|
|
|
|
{
|
|
|
|
if (!dragged_object->OnMouseEvent(event))
|
|
|
|
{
|
|
|
|
scroll_timer.Stop();
|
2013-11-21 18:13:36 +01:00
|
|
|
SetDraggedObject(nullptr);
|
2012-10-02 16:40:50 +02:00
|
|
|
SetCursor(wxNullCursor);
|
|
|
|
}
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
// Something is wrong, we might have lost capture somehow.
|
|
|
|
// Fix state and pretend it didn't happen.
|
2013-11-21 18:13:36 +01:00
|
|
|
SetDraggedObject(nullptr);
|
2012-10-02 16:40:50 +02:00
|
|
|
SetCursor(wxNullCursor);
|
|
|
|
}
|
|
|
|
|
|
|
|
const wxPoint mousepos = event.GetPosition();
|
2013-11-21 18:13:36 +01:00
|
|
|
AudioDisplayInteractionObject *new_obj = nullptr;
|
2012-10-02 16:40:50 +02:00
|
|
|
// Check for scrollbar action
|
|
|
|
if (scrollbar->GetBounds().Contains(mousepos))
|
|
|
|
{
|
|
|
|
new_obj = scrollbar.get();
|
|
|
|
}
|
|
|
|
// Check for timeline action
|
|
|
|
else if (timeline->GetBounds().Contains(mousepos))
|
|
|
|
{
|
|
|
|
SetCursor(wxCursor(wxCURSOR_SIZEWE));
|
|
|
|
new_obj = timeline.get();
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!controller->IsPlaying())
|
|
|
|
RemoveTrackCursor();
|
|
|
|
if (new_obj->OnMouseEvent(event))
|
|
|
|
SetDraggedObject(new_obj);
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2011-01-18 00:54:05 +01:00
|
|
|
void AudioDisplay::OnKeyDown(wxKeyEvent& event)
|
|
|
|
{
|
2012-03-13 00:34:34 +01:00
|
|
|
hotkey::check("Audio", context, event);
|
2011-01-18 00:54:05 +01:00
|
|
|
}
|
|
|
|
|
2011-12-22 22:09:31 +01:00
|
|
|
void AudioDisplay::OnSize(wxSizeEvent &)
|
2010-12-08 04:36:10 +01:00
|
|
|
{
|
|
|
|
// We changed size, update the sub-controls' internal data and redraw
|
|
|
|
wxSize size = GetClientSize();
|
2006-01-16 22:02:54 +01:00
|
|
|
|
2010-12-08 04:36:10 +01:00
|
|
|
timeline->SetDisplaySize(wxSize(size.x, scrollbar->GetBounds().y));
|
2012-01-13 22:17:40 +01:00
|
|
|
scrollbar->SetDisplaySize(size);
|
|
|
|
|
2012-02-02 00:58:58 +01:00
|
|
|
if (controller->GetTimingController())
|
|
|
|
{
|
|
|
|
TimeRange sel(controller->GetTimingController()->GetPrimaryPlaybackRange());
|
|
|
|
scrollbar->SetSelection(AbsoluteXFromTime(sel.begin()), AbsoluteXFromTime(sel.length()));
|
|
|
|
}
|
2006-01-16 22:02:54 +01:00
|
|
|
|
2010-12-08 04:36:10 +01:00
|
|
|
audio_height = size.GetHeight();
|
|
|
|
audio_height -= scrollbar->GetBounds().GetHeight();
|
|
|
|
audio_height -= timeline->GetHeight();
|
|
|
|
audio_renderer->SetHeight(audio_height);
|
2006-01-16 22:02:54 +01:00
|
|
|
|
2010-12-08 04:36:10 +01:00
|
|
|
audio_top = timeline->GetHeight();
|
2006-01-16 22:02:54 +01:00
|
|
|
|
2010-12-08 04:36:10 +01:00
|
|
|
Refresh();
|
|
|
|
}
|
2006-01-16 22:02:54 +01:00
|
|
|
|
2011-12-22 22:09:31 +01:00
|
|
|
void AudioDisplay::OnFocus(wxFocusEvent &)
|
2010-12-08 04:36:10 +01:00
|
|
|
{
|
|
|
|
// The scrollbar indicates focus so repaint that
|
2011-11-18 23:56:45 +01:00
|
|
|
RefreshRect(scrollbar->GetBounds(), false);
|
2010-12-08 04:36:10 +01:00
|
|
|
}
|
2006-01-16 22:02:54 +01:00
|
|
|
|
2014-05-26 18:14:51 +02:00
|
|
|
int AudioDisplay::GetDuration() const
|
|
|
|
{
|
|
|
|
if (!provider) return 0;
|
|
|
|
return (provider->GetNumSamples() * 1000 + provider->GetSampleRate() - 1) / provider->GetSampleRate();
|
|
|
|
}
|
|
|
|
|
2014-07-09 16:22:49 +02:00
|
|
|
void AudioDisplay::OnAudioOpen(agi::AudioProvider *provider)
|
2010-12-08 04:36:10 +01:00
|
|
|
{
|
2014-04-22 21:34:20 +02:00
|
|
|
this->provider = provider;
|
|
|
|
|
2011-11-30 01:40:22 +01:00
|
|
|
if (!audio_renderer_provider)
|
|
|
|
ReloadRenderingSettings();
|
2006-01-16 22:02:54 +01:00
|
|
|
|
2010-12-08 04:36:10 +01:00
|
|
|
audio_renderer->SetAudioProvider(provider);
|
|
|
|
audio_renderer->SetCacheMaxSize(OPT_GET("Audio/Renderer/Spectrum/Memory Max")->GetInt() * 1024 * 1024);
|
2006-01-16 22:02:54 +01:00
|
|
|
|
2014-05-26 18:14:51 +02:00
|
|
|
timeline->ChangeAudio(GetDuration());
|
2010-12-08 04:36:10 +01:00
|
|
|
|
2012-02-02 00:58:58 +01:00
|
|
|
ms_per_pixel = 0;
|
2010-12-08 04:36:10 +01:00
|
|
|
SetZoomLevel(zoom_level);
|
|
|
|
|
|
|
|
Refresh();
|
2011-11-18 23:57:54 +01:00
|
|
|
|
|
|
|
if (provider)
|
|
|
|
{
|
|
|
|
if (connections.empty())
|
|
|
|
{
|
2014-05-22 01:23:28 +02:00
|
|
|
connections = agi::signal::make_vector({
|
|
|
|
controller->AddPlaybackPositionListener(&AudioDisplay::OnPlaybackPosition, this),
|
|
|
|
controller->AddPlaybackStopListener(&AudioDisplay::RemoveTrackCursor, this),
|
|
|
|
controller->AddTimingControllerListener(&AudioDisplay::OnTimingController, this),
|
|
|
|
OPT_SUB("Audio/Spectrum", &AudioDisplay::ReloadRenderingSettings, this),
|
|
|
|
OPT_SUB("Audio/Display/Waveform Style", &AudioDisplay::ReloadRenderingSettings, this),
|
|
|
|
OPT_SUB("Colour/Audio Display/Spectrum", &AudioDisplay::ReloadRenderingSettings, this),
|
|
|
|
OPT_SUB("Colour/Audio Display/Waveform", &AudioDisplay::ReloadRenderingSettings, this),
|
|
|
|
OPT_SUB("Audio/Renderer/Spectrum/Quality", &AudioDisplay::ReloadRenderingSettings, 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
|
|
|
OPT_SUB("Timing/Tap To Time", &AudioDisplay::OnTapMarkerChanged, this),
|
2014-05-22 01:23:28 +02:00
|
|
|
});
|
2012-02-10 01:04:13 +01:00
|
|
|
OnTimingController();
|
2011-11-18 23:57:54 +01:00
|
|
|
}
|
2014-04-22 21:34:20 +02:00
|
|
|
|
|
|
|
last_sample_decoded = provider->GetDecodedSamples();
|
|
|
|
audio_load_position = -1;
|
|
|
|
audio_load_speed = 0;
|
|
|
|
audio_load_start_time = std::chrono::steady_clock::now();
|
|
|
|
if (last_sample_decoded != provider->GetNumSamples())
|
|
|
|
load_timer.Start(100);
|
2011-11-18 23:57:54 +01:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
connections.clear();
|
|
|
|
}
|
2006-01-16 22:02:54 +01:00
|
|
|
}
|
|
|
|
|
2012-02-02 00:59:12 +01:00
|
|
|
void AudioDisplay::OnTimingController()
|
|
|
|
{
|
|
|
|
AudioTimingController *timing_controller = controller->GetTimingController();
|
|
|
|
if (timing_controller)
|
|
|
|
{
|
|
|
|
timing_controller->AddMarkerMovedListener(&AudioDisplay::OnMarkerMoved, this);
|
|
|
|
timing_controller->AddUpdatedPrimaryRangeListener(&AudioDisplay::OnSelectionChanged, this);
|
|
|
|
timing_controller->AddUpdatedStyleRangesListener(&AudioDisplay::OnStyleRangesChanged, 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
|
|
|
timing_controller->AddUpdatedTapMarkerListener(&AudioDisplay::OnTapMarkerChanged, this);
|
2012-02-02 00:59:12 +01:00
|
|
|
|
|
|
|
OnStyleRangesChanged();
|
|
|
|
OnMarkerMoved();
|
2014-04-23 01:42:47 +02:00
|
|
|
OnSelectionChanged();
|
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
|
|
|
OnTapMarkerChanged();
|
2012-02-02 00:59:12 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2012-02-02 00:58:58 +01:00
|
|
|
void AudioDisplay::OnPlaybackPosition(int ms)
|
2010-12-08 04:36:10 +01:00
|
|
|
{
|
2012-02-02 00:58:58 +01:00
|
|
|
int pixel_position = AbsoluteXFromTime(ms);
|
2011-11-30 01:40:32 +01:00
|
|
|
SetTrackCursor(pixel_position, false);
|
|
|
|
|
|
|
|
if (OPT_GET("Audio/Lock Scroll on Cursor")->GetBool())
|
|
|
|
{
|
|
|
|
int client_width = GetClientSize().GetWidth();
|
|
|
|
int edge_size = client_width / 20;
|
|
|
|
if (scroll_left > 0 && pixel_position < scroll_left + edge_size)
|
|
|
|
{
|
2011-12-27 02:38:08 +01:00
|
|
|
ScrollPixelToLeft(std::max(pixel_position - edge_size, 0));
|
2011-11-30 01:40:32 +01:00
|
|
|
}
|
|
|
|
else if (scroll_left + client_width < std::min(pixel_audio_width - 1, pixel_position + edge_size))
|
|
|
|
{
|
2011-12-27 02:38:08 +01:00
|
|
|
ScrollPixelToLeft(std::min(pixel_position - client_width + edge_size, pixel_audio_width - client_width - 1));
|
2011-11-30 01:40:32 +01:00
|
|
|
}
|
|
|
|
}
|
2006-01-16 22:02:54 +01:00
|
|
|
}
|
|
|
|
|
2010-12-08 04:36:10 +01:00
|
|
|
void AudioDisplay::OnSelectionChanged()
|
|
|
|
{
|
2012-02-02 00:58:58 +01:00
|
|
|
TimeRange sel(controller->GetPrimaryPlaybackRange());
|
|
|
|
scrollbar->SetSelection(AbsoluteXFromTime(sel.begin()), AbsoluteXFromTime(sel.length()));
|
2011-11-18 23:56:45 +01:00
|
|
|
|
2012-03-07 23:41:03 +01:00
|
|
|
if (audio_marker)
|
|
|
|
{
|
2012-05-01 04:50:03 +02:00
|
|
|
if (!scroll_timer.IsRunning())
|
2012-03-07 23:41:03 +01:00
|
|
|
{
|
2012-05-01 04:50:03 +02:00
|
|
|
// If the dragged object is outside the visible area, start the
|
|
|
|
// scroll timer to shift it back into view
|
|
|
|
int rel_x = RelativeXFromTime(audio_marker->GetPosition());
|
|
|
|
if (rel_x < 0 || rel_x >= GetClientSize().GetWidth())
|
|
|
|
{
|
|
|
|
// 50ms is the default for this on Windows (hardcoded since
|
|
|
|
// wxSystemSettings doesn't expose DragScrollDelay etc.)
|
|
|
|
scroll_timer.Start(50, true);
|
|
|
|
}
|
2012-03-07 23:41:03 +01:00
|
|
|
}
|
|
|
|
}
|
2012-03-12 00:05:02 +01:00
|
|
|
else if (OPT_GET("Audio/Auto/Scroll")->GetBool() && sel.end() != 0)
|
2011-11-18 23:56:45 +01:00
|
|
|
{
|
2012-02-02 00:58:58 +01:00
|
|
|
ScrollTimeRangeInView(sel);
|
2011-11-18 23:56:45 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
RefreshRect(scrollbar->GetBounds(), false);
|
|
|
|
}
|
|
|
|
|
2012-05-01 04:50:03 +02:00
|
|
|
void AudioDisplay::OnScrollTimer(wxTimerEvent &event)
|
|
|
|
{
|
|
|
|
if (!audio_marker) return;
|
|
|
|
|
|
|
|
int rel_x = RelativeXFromTime(audio_marker->GetPosition());
|
|
|
|
int width = GetClientSize().GetWidth();
|
|
|
|
|
|
|
|
// If the dragged object is outside the visible area, scroll it into
|
|
|
|
// view with a 5% margin
|
|
|
|
if (rel_x < 0)
|
|
|
|
{
|
|
|
|
ScrollBy(rel_x - width / 20);
|
|
|
|
}
|
|
|
|
else if (rel_x >= width)
|
|
|
|
{
|
|
|
|
ScrollBy(rel_x - width + width / 20);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2011-11-18 23:56:45 +01:00
|
|
|
void AudioDisplay::OnStyleRangesChanged()
|
|
|
|
{
|
2011-11-18 23:58:22 +01:00
|
|
|
if (!controller->GetTimingController()) return;
|
|
|
|
|
2011-11-18 23:56:45 +01:00
|
|
|
AudioStyleRangeMerger asrm;
|
|
|
|
controller->GetTimingController()->GetRenderingStyles(asrm);
|
|
|
|
|
2012-02-02 23:57:53 +01:00
|
|
|
style_ranges.clear();
|
2014-07-16 20:09:15 +02:00
|
|
|
for (auto pair : asrm) style_ranges.push_back(pair);
|
2010-12-30 23:19:24 +01:00
|
|
|
|
2012-02-02 23:57:53 +01:00
|
|
|
RefreshRect(wxRect(0, audio_top, GetClientSize().GetWidth(), audio_height), false);
|
2010-12-07 20:09:28 +01:00
|
|
|
}
|
2010-12-30 23:19:12 +01:00
|
|
|
|
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 AudioDisplay::OnTapMarkerChanged()
|
|
|
|
{
|
|
|
|
RefreshRect(wxRect(0, audio_top, GetClientSize().GetWidth(), audio_height), false);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2010-12-30 23:19:12 +01:00
|
|
|
void AudioDisplay::OnMarkerMoved()
|
|
|
|
{
|
2011-11-18 23:56:45 +01:00
|
|
|
RefreshRect(wxRect(0, audio_top, GetClientSize().GetWidth(), audio_height), false);
|
2010-12-30 23:19:12 +01:00
|
|
|
}
|