forked from mia/Aegisub
* In karaoke mode, instead of scrolling start to end of active line into view, scroll start of selection to end of line into view. This gives sensible results when horizontal zoom is great enough that the entire line isn't visible at once and the syllable is too far from the start of the line. This fixes #677. * If the line is too long to fit in the display, always left-align it instead of sometimes left-aligning and sometimes centering. (I think the old behaviour depended on the actual scroll position.) Originally committed to SVN as r2928.
2403 lines
61 KiB
C++
2403 lines
61 KiB
C++
// Copyright (c) 2005, Rodrigo Braz Monteiro
|
|
// 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
|
|
//
|
|
// Website: http://aegisub.cellosoft.com
|
|
// Contact: mailto:zeratul@cellosoft.com
|
|
//
|
|
|
|
|
|
///////////
|
|
// Headers
|
|
#include "config.h"
|
|
|
|
#include <wx/tglbtn.h>
|
|
#include <wx/filename.h>
|
|
#include <math.h>
|
|
#include <vector>
|
|
#include "audio_display.h"
|
|
#include "audio_provider_stream.h"
|
|
#include "main.h"
|
|
#include "ass_dialogue.h"
|
|
#include "subs_grid.h"
|
|
#include "ass_file.h"
|
|
#include "subs_edit_box.h"
|
|
#include "options.h"
|
|
#include "audio_karaoke.h"
|
|
#include "audio_box.h"
|
|
#include "fft.h"
|
|
#include "video_context.h"
|
|
#include "vfr.h"
|
|
#include "colorspace.h"
|
|
#include "hotkeys.h"
|
|
#include "utils.h"
|
|
#include "timeedit_ctrl.h"
|
|
#include "standard_paths.h"
|
|
#ifdef _DEBUG
|
|
#include "audio_provider_dummy.h"
|
|
#endif
|
|
|
|
|
|
#ifdef __WXMAC__
|
|
# define AudioDisplayWindowStyle wxWANTS_CHARS
|
|
#else
|
|
# define AudioDisplayWindowStyle wxSUNKEN_BORDER | wxWANTS_CHARS
|
|
#endif
|
|
|
|
///////////////
|
|
// Constructor
|
|
AudioDisplay::AudioDisplay(wxWindow *parent)
|
|
: wxWindow (parent, -1, wxDefaultPosition, wxSize(200,Options.AsInt(_T("Audio Display Height"))), AudioDisplayWindowStyle , _T("Audio Display"))
|
|
{
|
|
// Set variables
|
|
origImage = NULL;
|
|
spectrumDisplay = NULL;
|
|
spectrumDisplaySelected = NULL;
|
|
spectrumRenderer = NULL;
|
|
ScrollBar = NULL;
|
|
dialogue = NULL;
|
|
karaoke = NULL;
|
|
peak = NULL;
|
|
min = NULL;
|
|
hasSel = false;
|
|
diagUpdated = false;
|
|
NeedCommit = false;
|
|
loaded = false;
|
|
temporary = false;
|
|
blockUpdate = false;
|
|
dontReadTimes = false;
|
|
holding = false;
|
|
draggingScale = false;
|
|
scrubbing = false;
|
|
Position = 0;
|
|
PositionSample = 0;
|
|
oldCurPos = 0;
|
|
scale = 1.0f;
|
|
provider = NULL;
|
|
player = NULL;
|
|
hold = 0;
|
|
samples = 0;
|
|
samplesPercent = 100;
|
|
hasFocus = (wxWindow::FindFocus() == this);
|
|
needImageUpdate = false;
|
|
needImageUpdateWeak = true;
|
|
|
|
// Init
|
|
UpdateTimer.SetOwner(this,Audio_Update_Timer);
|
|
GetClientSize(&w,&h);
|
|
h -= Options.AsBool(_T("Audio Draw Timeline")) ? 20 : 0;
|
|
SetSamplesPercent(50,false);
|
|
|
|
// Set cursor
|
|
//wxCursor cursor(wxCURSOR_BLANK);
|
|
//SetCursor(cursor);
|
|
|
|
//wxLog::SetActiveTarget(new wxLogWindow(NULL,_T("Log"),true,false));
|
|
}
|
|
|
|
|
|
//////////////
|
|
// Destructor
|
|
AudioDisplay::~AudioDisplay() {
|
|
if (player) player->CloseStream();
|
|
delete provider;
|
|
delete player;
|
|
delete origImage;
|
|
delete spectrumRenderer;
|
|
delete spectrumDisplay;
|
|
delete spectrumDisplaySelected;
|
|
delete peak;
|
|
delete min;
|
|
provider = NULL;
|
|
player = NULL;
|
|
origImage = NULL;
|
|
spectrumRenderer = NULL;
|
|
spectrumDisplay = NULL;
|
|
spectrumDisplaySelected = NULL;
|
|
peak = NULL;
|
|
min = NULL;
|
|
}
|
|
|
|
|
|
/////////
|
|
// Reset
|
|
void AudioDisplay::Reset() {
|
|
wxLogDebug(_T("AudioDisplay::Reset"));
|
|
hasSel = false;
|
|
diagUpdated = false;
|
|
NeedCommit = false;
|
|
karaoke->enabled = false;
|
|
karaoke->syllables.clear();
|
|
box->karaokeMode = false;
|
|
box->KaraokeButton->SetValue(false);
|
|
dialogue = NULL;
|
|
}
|
|
|
|
|
|
////////////////
|
|
// Update image
|
|
void AudioDisplay::UpdateImage(bool weak) {
|
|
// Update samples
|
|
UpdateSamples();
|
|
|
|
// Set image as needing to be redrawn
|
|
needImageUpdate = true;
|
|
if (weak == false && needImageUpdateWeak == true) {
|
|
needImageUpdateWeak = false;
|
|
}
|
|
Refresh(false);
|
|
}
|
|
|
|
void AudioDisplay::DoUpdateImage() {
|
|
// Loaded?
|
|
if (!loaded || !provider) return;
|
|
|
|
// Needs updating?
|
|
if (!needImageUpdate) return;
|
|
bool weak = needImageUpdateWeak;
|
|
|
|
// Prepare bitmap
|
|
int timelineHeight = Options.AsBool(_T("Audio Draw Timeline")) ? 20 : 0;
|
|
int displayH = h+timelineHeight;
|
|
if (origImage) {
|
|
if (origImage->GetWidth() != w || origImage->GetHeight() != displayH) {
|
|
delete origImage;
|
|
origImage = NULL;
|
|
}
|
|
}
|
|
|
|
// Options
|
|
bool draw_boundary_lines = Options.AsBool(_T("Audio Draw Secondary Lines"));
|
|
bool draw_selection_background = Options.AsBool(_T("Audio Draw Selection Background"));
|
|
bool drawKeyframes = Options.AsBool(_T("Audio Draw Keyframes"));
|
|
|
|
// Invalid dimensions
|
|
if (w == 0 || displayH == 0) return;
|
|
|
|
// New bitmap
|
|
if (!origImage) origImage = new wxBitmap(w,displayH,-1);
|
|
|
|
// Is spectrum?
|
|
bool spectrum = false;
|
|
if (provider && Options.AsBool(_T("Audio Spectrum"))) {
|
|
spectrum = true;
|
|
}
|
|
|
|
// Draw image to be displayed
|
|
wxMemoryDC dc;
|
|
dc.SelectObject(*origImage);
|
|
|
|
// Black background
|
|
dc.SetPen(*wxTRANSPARENT_PEN);
|
|
dc.SetBrush(wxBrush(Options.AsColour(_T("Audio Background"))));
|
|
dc.DrawRectangle(0,0,w,h);
|
|
|
|
// Selection position
|
|
hasSel = false;
|
|
hasKaraoke = karaoke->enabled;
|
|
selStart = 0;
|
|
selEnd = 0;
|
|
lineStart = 0;
|
|
lineEnd = 0;
|
|
selStartCap = 0;
|
|
selEndCap = 0;
|
|
int64_t drawSelStart = 0;
|
|
int64_t drawSelEnd = 0;
|
|
if (dialogue) {
|
|
GetDialoguePos(lineStart,lineEnd,false);
|
|
hasSel = true;
|
|
if (hasKaraoke) {
|
|
GetKaraokePos(selStartCap,selEndCap,true);
|
|
GetKaraokePos(drawSelStart,drawSelEnd,false);
|
|
selStart = lineStart;
|
|
selEnd = lineEnd;
|
|
}
|
|
else {
|
|
GetDialoguePos(selStartCap,selEndCap,true);
|
|
selStart = lineStart;
|
|
selEnd = lineEnd;
|
|
drawSelStart = lineStart;
|
|
drawSelEnd = lineEnd;
|
|
}
|
|
}
|
|
|
|
// Draw selection bg
|
|
if (hasSel && drawSelStart < drawSelEnd && draw_selection_background) {
|
|
if (NeedCommit && !karaoke->enabled) dc.SetBrush(wxBrush(Options.AsColour(_T("Audio Selection Background Modified"))));
|
|
else dc.SetBrush(wxBrush(Options.AsColour(_T("Audio Selection Background"))));
|
|
dc.DrawRectangle(drawSelStart,0,drawSelEnd-drawSelStart,h);
|
|
}
|
|
|
|
// Draw spectrum
|
|
if (spectrum) {
|
|
DrawSpectrum(dc,weak);
|
|
}
|
|
|
|
// Waveform
|
|
else if (provider) {
|
|
DrawWaveform(dc,weak);
|
|
}
|
|
|
|
// Nothing
|
|
else {
|
|
dc.DrawLine(0,h/2,w,h/2);
|
|
}
|
|
|
|
// Draw seconds boundaries
|
|
if (draw_boundary_lines) {
|
|
int64_t start = Position*samples;
|
|
int rate = provider->GetSampleRate();
|
|
int pixBounds = rate / samples;
|
|
dc.SetPen(wxPen(Options.AsColour(_T("Audio Seconds Boundaries")),1,wxDOT));
|
|
if (pixBounds >= 8) {
|
|
for (int x=0;x<w;x++) {
|
|
if (((x*samples)+start) % rate < samples) {
|
|
dc.DrawLine(x,0,x,h);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Draw current frame
|
|
if (Options.AsBool(_T("Audio Draw Video Position"))) {
|
|
if (VideoContext::Get()->IsLoaded()) {
|
|
dc.SetPen(wxPen(Options.AsColour(_T("Audio Play Cursor")),2,wxLONG_DASH));
|
|
int x = GetXAtMS(VFR_Output.GetTimeAtFrame(VideoContext::Get()->GetFrameN()));
|
|
dc.DrawLine(x,0,x,h);
|
|
}
|
|
}
|
|
|
|
// Draw keyframes
|
|
if (drawKeyframes && VideoContext::Get()->KeyFramesLoaded()) {
|
|
DrawKeyframes(dc);
|
|
}
|
|
|
|
// Draw previous line
|
|
DrawInactiveLines(dc);
|
|
|
|
if (hasSel) {
|
|
// Draw boundaries
|
|
if (true) {
|
|
// Draw start boundary
|
|
int selWidth = Options.AsInt(_T("Audio Line boundaries Thickness"));
|
|
dc.SetPen(wxPen(Options.AsColour(_T("Audio Line boundary start"))));
|
|
dc.SetBrush(wxBrush(Options.AsColour(_T("Audio Line boundary start"))));
|
|
dc.DrawRectangle(lineStart-selWidth/2+1,0,selWidth,h);
|
|
wxPoint points1[3] = { wxPoint(lineStart,0), wxPoint(lineStart+10,0), wxPoint(lineStart,10) };
|
|
wxPoint points2[3] = { wxPoint(lineStart,h-1), wxPoint(lineStart+10,h-1), wxPoint(lineStart,h-11) };
|
|
dc.DrawPolygon(3,points1);
|
|
dc.DrawPolygon(3,points2);
|
|
|
|
// Draw end boundary
|
|
dc.SetPen(wxPen(Options.AsColour(_T("Audio Line boundary end"))));
|
|
dc.SetBrush(wxBrush(Options.AsColour(_T("Audio Line boundary end"))));
|
|
dc.DrawRectangle(lineEnd-selWidth/2+1,0,selWidth,h);
|
|
wxPoint points3[3] = { wxPoint(lineEnd,0), wxPoint(lineEnd-10,0), wxPoint(lineEnd,10) };
|
|
wxPoint points4[3] = { wxPoint(lineEnd,h-1), wxPoint(lineEnd-10,h-1), wxPoint(lineEnd,h-11) };
|
|
dc.DrawPolygon(3,points3);
|
|
dc.DrawPolygon(3,points4);
|
|
}
|
|
|
|
// Draw karaoke
|
|
if (hasKaraoke) {
|
|
try {
|
|
// Prepare
|
|
wxPen curPen(Options.AsColour(_T("Audio Syllable boundaries")),1,wxDOT);
|
|
dc.SetPen(curPen);
|
|
wxFont curFont(9,wxFONTFAMILY_DEFAULT,wxFONTSTYLE_NORMAL,wxFONTWEIGHT_BOLD,false,_T("Verdana"),wxFONTENCODING_SYSTEM);
|
|
dc.SetFont(curFont);
|
|
if (!spectrum) dc.SetTextForeground(Options.AsColour(_T("Audio Syllable text")));
|
|
else dc.SetTextForeground(wxColour(255,255,255));
|
|
size_t karn = karaoke->syllables.size();
|
|
int64_t pos1,pos2;
|
|
int len,curpos;
|
|
wxCoord tw=0,th=0;
|
|
AudioKaraokeSyllable *curSyl;
|
|
wxString temptext;
|
|
|
|
// Draw syllables
|
|
for (size_t i=0;i<karn;i++) {
|
|
curSyl = &karaoke->syllables.at(i);
|
|
len = curSyl->duration*10;
|
|
curpos = curSyl->start_time*10;
|
|
if (len != -1) {
|
|
pos1 = GetXAtMS(curStartMS+curpos);
|
|
pos2 = GetXAtMS(curStartMS+len+curpos);
|
|
dc.DrawLine(pos2,0,pos2,h);
|
|
temptext = curSyl->text;
|
|
temptext.Trim(true);
|
|
temptext.Trim(false);
|
|
GetTextExtent(temptext,&tw,&th,NULL,NULL,&curFont);
|
|
dc.DrawText(temptext,(pos1+pos2-tw)/2,4);
|
|
}
|
|
}
|
|
}
|
|
catch (...) {
|
|
// FIXME?
|
|
}
|
|
}
|
|
}
|
|
|
|
// Modified text
|
|
if (NeedCommit) {
|
|
dc.SetFont(wxFont(9,wxDEFAULT,wxFONTSTYLE_NORMAL,wxFONTWEIGHT_BOLD,false,_T("Verdana"))); // FIXME: hardcoded font name
|
|
dc.SetTextForeground(wxColour(255,0,0));
|
|
if (selStart <= selEnd) {
|
|
dc.DrawText(_T("Modified"),4,4);
|
|
}
|
|
else {
|
|
dc.DrawText(_T("Negative time"),4,4);
|
|
}
|
|
}
|
|
|
|
// Draw timescale
|
|
if (timelineHeight) {
|
|
DrawTimescale(dc);
|
|
}
|
|
|
|
// Draw selection border
|
|
if (hasFocus) {
|
|
dc.SetPen(*wxGREEN_PEN);
|
|
dc.SetBrush(*wxTRANSPARENT_BRUSH);
|
|
dc.DrawRectangle(0,0,w,h);
|
|
}
|
|
|
|
// Done
|
|
needImageUpdate = false;
|
|
needImageUpdateWeak = true;
|
|
}
|
|
|
|
|
|
///////////////////////
|
|
// Draw Inactive Lines
|
|
void AudioDisplay::DrawInactiveLines(wxDC &dc) {
|
|
// Check if there is anything to do
|
|
int shadeType = Options.AsInt(_T("Audio Inactive Lines Display Mode"));
|
|
if (shadeType == 0) return;
|
|
|
|
// Spectrum?
|
|
bool spectrum = false;
|
|
if (provider && Options.AsBool(_T("Audio Spectrum"))) {
|
|
spectrum = true;
|
|
}
|
|
|
|
// Set options
|
|
dc.SetBrush(wxBrush(Options.AsColour(_T("Audio Line boundary inactive line"))));
|
|
int selWidth = Options.AsInt(_T("Audio Line boundaries Thickness"));
|
|
AssDialogue *shade;
|
|
int shadeX1,shadeX2;
|
|
int shadeFrom,shadeTo;
|
|
|
|
// Only previous
|
|
if (shadeType == 1) {
|
|
shadeFrom = this->line_n-1;
|
|
shadeTo = shadeFrom+1;
|
|
}
|
|
|
|
// All
|
|
else {
|
|
shadeFrom = 0;
|
|
shadeTo = grid->GetRows();
|
|
}
|
|
|
|
for (int j=shadeFrom;j<shadeTo;j++) {
|
|
if (j == line_n) continue;
|
|
if (j < 0) continue;
|
|
shade = grid->GetDialogue(j);
|
|
|
|
if (shade) {
|
|
// Get coordinates
|
|
shadeX1 = GetXAtMS(shade->Start.GetMS());
|
|
shadeX2 = GetXAtMS(shade->End.GetMS());
|
|
if (shadeX2 < 0 || shadeX1 > w) continue;
|
|
|
|
// Draw over waveform
|
|
if (!spectrum) {
|
|
// Selection
|
|
int selX1 = MAX(0,GetXAtMS(curStartMS));
|
|
int selX2 = MIN(w,GetXAtMS(curEndMS));
|
|
|
|
// Get ranges (x1->x2, x3->x4).
|
|
int x1 = MAX(0,shadeX1);
|
|
int x2 = MIN(w,shadeX2);
|
|
int x3 = MAX(x1,selX2);
|
|
int x4 = MAX(x2,selX2);
|
|
|
|
// Clip first range
|
|
x1 = MIN(x1,selX1);
|
|
x2 = MIN(x2,selX1);
|
|
|
|
// Set pen and draw
|
|
dc.SetPen(wxPen(Options.AsColour(_T("Audio Waveform Inactive"))));
|
|
for (int i=x1;i<x2;i++) dc.DrawLine(i,peak[i],i,min[i]-1);
|
|
for (int i=x3;i<x4;i++) dc.DrawLine(i,peak[i],i,min[i]-1);
|
|
}
|
|
|
|
// Draw boundaries
|
|
dc.SetPen(wxPen(Options.AsColour(_T("Audio Line boundary inactive line"))));
|
|
dc.DrawRectangle(shadeX1-selWidth/2+1,0,selWidth,h);
|
|
dc.DrawRectangle(shadeX2-selWidth/2+1,0,selWidth,h);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
//////////////////
|
|
// Draw keyframes
|
|
void AudioDisplay::DrawKeyframes(wxDC &dc) {
|
|
wxArrayInt KeyFrames = VideoContext::Get()->GetKeyFrames();
|
|
int nKeys = (int)KeyFrames.Count();
|
|
dc.SetPen(wxPen(wxColour(255,0,255),1));
|
|
|
|
// Get min and max frames to care about
|
|
int minFrame = VFR_Output.GetFrameAtTime(GetMSAtX(0),true);
|
|
int maxFrame = VFR_Output.GetFrameAtTime(GetMSAtX(w),true);
|
|
|
|
// Scan list
|
|
for (int i=0;i<nKeys;i++) {
|
|
int cur = KeyFrames[i];
|
|
if (cur >= minFrame && cur <= maxFrame) {
|
|
int x = GetXAtMS(VFR_Output.GetTimeAtFrame(cur,true));
|
|
dc.DrawLine(x,0,x,h);
|
|
}
|
|
else if (cur > maxFrame) break;
|
|
}
|
|
}
|
|
|
|
|
|
//////////////////
|
|
// Draw timescale
|
|
void AudioDisplay::DrawTimescale(wxDC &dc) {
|
|
// Set size
|
|
int timelineHeight = Options.AsBool(_T("Audio Draw Timeline")) ? 20 : 0;
|
|
|
|
// Set colours
|
|
dc.SetBrush(wxSystemSettings::GetColour(wxSYS_COLOUR_BTNFACE));
|
|
dc.SetPen(*wxTRANSPARENT_PEN);
|
|
dc.DrawRectangle(0,h,w,timelineHeight);
|
|
dc.SetPen(wxSystemSettings::GetColour(wxSYS_COLOUR_3DLIGHT));
|
|
dc.DrawLine(0,h,w,h);
|
|
dc.SetPen(wxSystemSettings::GetColour(wxSYS_COLOUR_3DHIGHLIGHT));
|
|
dc.DrawLine(0,h+1,w,h+1);
|
|
dc.SetPen(wxSystemSettings::GetColour(wxSYS_COLOUR_BTNTEXT));
|
|
dc.SetTextForeground(wxSystemSettings::GetColour(wxSYS_COLOUR_BTNTEXT));
|
|
wxFont scaleFont;
|
|
scaleFont.SetFaceName(_T("Tahoma")); // FIXME: hardcoded font name
|
|
scaleFont.SetPointSize(8);
|
|
dc.SetFont(scaleFont);
|
|
|
|
// Timescale ticks
|
|
int64_t start = Position*samples;
|
|
int rate = provider->GetSampleRate();
|
|
for (int i=1;i<32;i*=2) {
|
|
int pixBounds = rate / (samples * 4 / i);
|
|
if (pixBounds >= 8) {
|
|
for (int x=0;x<w;x++) {
|
|
int64_t pos = (x*samples)+start;
|
|
// Second boundary
|
|
if (pos % rate < samples) {
|
|
dc.DrawLine(x,h+2,x,h+8);
|
|
|
|
// Draw text
|
|
wxCoord textW,textH;
|
|
int hr = 0;
|
|
int m = 0;
|
|
int s = pos/rate;
|
|
while (s >= 3600) {
|
|
s -= 3600;
|
|
hr++;
|
|
}
|
|
while (s >= 60) {
|
|
s -= 60;
|
|
m++;
|
|
}
|
|
wxString text;
|
|
if (hr) text = wxString::Format(_T("%i:%02i:%02i"),hr,m,s);
|
|
else if (m) text = wxString::Format(_T("%i:%02i"),m,s);
|
|
else text = wxString::Format(_T("%i"),s);
|
|
dc.GetTextExtent(text,&textW,&textH,NULL,NULL,&scaleFont);
|
|
dc.DrawText(text,MAX(0,x-textW/2)+1,h+8);
|
|
}
|
|
|
|
// Other
|
|
else if (pos % (rate / 4 * i) < samples) {
|
|
dc.DrawLine(x,h+2,x,h+5);
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
////////////
|
|
// Waveform
|
|
void AudioDisplay::DrawWaveform(wxDC &dc,bool weak) {
|
|
// Prepare Waveform
|
|
if (!weak || peak == NULL || min == NULL) {
|
|
if (peak) delete peak;
|
|
if (min) delete min;
|
|
peak = new int[w];
|
|
min = new int[w];
|
|
}
|
|
|
|
// Get waveform
|
|
if (!weak) {
|
|
provider->GetWaveForm(min,peak,Position*samples,w,h,samples,scale);
|
|
}
|
|
|
|
// Draw pre-selection
|
|
if (!hasSel) selStartCap = w;
|
|
dc.SetPen(wxPen(Options.AsColour(_T("Audio Waveform"))));
|
|
for (int64_t i=0;i<selStartCap;i++) {
|
|
dc.DrawLine(i,peak[i],i,min[i]-1);
|
|
}
|
|
|
|
if (hasSel) {
|
|
// Draw selection
|
|
if (Options.AsBool(_T("Audio Draw Selection Background"))) {
|
|
if (NeedCommit && !karaoke->enabled) dc.SetPen(wxPen(Options.AsColour(_T("Audio Waveform Modified"))));
|
|
else dc.SetPen(wxPen(Options.AsColour(_T("Audio Waveform Selected"))));
|
|
}
|
|
for (int64_t i=selStartCap;i<selEndCap;i++) {
|
|
dc.DrawLine(i,peak[i],i,min[i]-1);
|
|
}
|
|
|
|
// Draw post-selection
|
|
dc.SetPen(wxPen(Options.AsColour(_T("Audio Waveform"))));
|
|
for (int64_t i=selEndCap;i<w;i++) {
|
|
dc.DrawLine(i,peak[i],i,min[i]-1);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
//////////////////////////
|
|
// Draw spectrum analyzer
|
|
void AudioDisplay::DrawSpectrum(wxDC &finaldc,bool weak) {
|
|
if (!weak || !spectrumDisplay || spectrumDisplay->GetWidth() != w || spectrumDisplay->GetHeight() != h) {
|
|
if (spectrumDisplay) {
|
|
delete spectrumDisplay;
|
|
delete spectrumDisplaySelected;
|
|
spectrumDisplay = 0;
|
|
spectrumDisplaySelected = 0;
|
|
}
|
|
weak = false;
|
|
}
|
|
|
|
if (!weak) {
|
|
if (!spectrumRenderer)
|
|
spectrumRenderer = new AudioSpectrum(provider);
|
|
|
|
spectrumRenderer->SetScaling(scale);
|
|
|
|
unsigned char *img = (unsigned char *)malloc(h*w*3); // wxImage requires using malloc
|
|
|
|
// Use a slightly slower, but simple way
|
|
// Always draw the spectrum for the entire width
|
|
// Hack: without those divs by 2 the display is horizontally compressed
|
|
spectrumRenderer->RenderRange(Position*samples, (Position+w)*samples, false, img, 0, w, w, h);
|
|
|
|
// The spectrum bitmap will have been deleted above already, so just make a new one
|
|
wxImage imgobj(w, h, img, false);
|
|
spectrumDisplay = new wxBitmap(imgobj);
|
|
}
|
|
|
|
if (hasSel && selStartCap < selEndCap && !spectrumDisplaySelected) {
|
|
// There is a visible selection and we don't have a rendered one
|
|
// This should be done regardless whether we're "weak" or not
|
|
// Assume a few things were already set up when things were first rendered though
|
|
unsigned char *img = (unsigned char *)malloc(h*w*3);
|
|
spectrumRenderer->RenderRange(Position*samples, (Position+w)*samples, true, img, 0, w, w, h);
|
|
wxImage imgobj(w, h, img, false);
|
|
spectrumDisplaySelected = new wxBitmap(imgobj);
|
|
}
|
|
|
|
// Draw
|
|
wxMemoryDC dc;
|
|
dc.SelectObject(*spectrumDisplay);
|
|
finaldc.Blit(0,0,w,h,&dc,0,0);
|
|
|
|
if (hasSel && spectrumDisplaySelected && selStartCap < selEndCap) {
|
|
dc.SelectObject(*spectrumDisplaySelected);
|
|
finaldc.Blit(selStartCap, 0, selEndCap-selStartCap, h, &dc, selStartCap, 0);
|
|
}
|
|
}
|
|
|
|
//////////////////////////
|
|
// Get selection position
|
|
void AudioDisplay::GetDialoguePos(int64_t &selStart,int64_t &selEnd, bool cap) {
|
|
selStart = GetXAtMS(curStartMS);
|
|
selEnd = GetXAtMS(curEndMS);
|
|
|
|
if (cap) {
|
|
if (selStart < 0) selStart = 0;
|
|
if (selEnd < 0) selEnd = 0;
|
|
if (selStart >= w) selStart = w-1;
|
|
if (selEnd >= w) selEnd = w-1;
|
|
}
|
|
}
|
|
|
|
|
|
////////////////////////
|
|
// Get karaoke position
|
|
void AudioDisplay::GetKaraokePos(int64_t &karStart,int64_t &karEnd, bool cap) {
|
|
try {
|
|
// Wrap around
|
|
int nsyls = (int)karaoke->syllables.size();
|
|
if (karaoke->curSyllable == -1) {
|
|
karaoke->SetSyllable(nsyls-1);
|
|
}
|
|
if (karaoke->curSyllable >= nsyls) karaoke->curSyllable = nsyls-1;
|
|
|
|
// Get positions
|
|
int pos = karaoke->syllables.at(karaoke->curSyllable).start_time;
|
|
int len = karaoke->syllables.at(karaoke->curSyllable).duration;
|
|
karStart = GetXAtMS(curStartMS+pos*10);
|
|
karEnd = GetXAtMS(curStartMS+pos*10+len*10);
|
|
|
|
// Cap
|
|
if (cap) {
|
|
if (karStart < 0) karStart = 0;
|
|
if (karEnd < 0) karEnd = 0;
|
|
if (karStart >= w) karStart = w-1;
|
|
if (karEnd >= w) karEnd = w-1;
|
|
}
|
|
}
|
|
catch (...) {
|
|
}
|
|
}
|
|
|
|
|
|
//////////
|
|
// Update
|
|
void AudioDisplay::Update() {
|
|
if (blockUpdate) return;
|
|
if (loaded) {
|
|
if (Options.AsBool(_T("Audio Autoscroll")))
|
|
MakeDialogueVisible();
|
|
else
|
|
UpdateImage(true);
|
|
}
|
|
}
|
|
|
|
|
|
//////////////////////
|
|
// Recreate the image
|
|
void AudioDisplay::RecreateImage() {
|
|
GetClientSize(&w,&h);
|
|
h -= Options.AsBool(_T("Audio Draw Timeline")) ? 20 : 0;
|
|
delete origImage;
|
|
origImage = NULL;
|
|
UpdateImage(false);
|
|
}
|
|
|
|
|
|
/////////////////////////
|
|
// Make dialogue visible
|
|
void AudioDisplay::MakeDialogueVisible(bool force) {
|
|
wxLogDebug(_T("AudioDisplay::MakeDialogueVisible(force=%d)"), force?1:0);
|
|
// Variables
|
|
int startShow=0, endShow=0;
|
|
if (karaoke->enabled) {
|
|
// In karaoke mode the syllable and as much as possible towards the end of the line should be shown
|
|
int dummy = 0;
|
|
GetTimesSelection(startShow, dummy);
|
|
GetTimesDialogue(dummy, endShow);
|
|
} else {
|
|
GetTimesSelection(startShow,endShow);
|
|
}
|
|
int startPos = GetSampleAtMS(startShow);
|
|
int endPos = GetSampleAtMS(endShow);
|
|
int startX = GetXAtMS(startShow);
|
|
int endX = GetXAtMS(endShow);
|
|
|
|
if (force || startX < 50 || endX > w-50) {
|
|
if (startX < 50 || endX - startX >= w) {
|
|
// Make sure the left edge of the selection is at least 50 pixels from the edge of the display
|
|
UpdatePosition(startPos - 50*samples, true);
|
|
} else {
|
|
// Otherwise center the selection in display
|
|
UpdatePosition((startPos+endPos-w*samples)/2,true);
|
|
}
|
|
}
|
|
|
|
// Update
|
|
UpdateImage();
|
|
}
|
|
|
|
|
|
////////////////
|
|
// Set position
|
|
void AudioDisplay::SetPosition(int pos) {
|
|
wxLogDebug(_T("AudioDisplay::SetPosition(pos=%d)"), pos);
|
|
Position = pos;
|
|
PositionSample = pos * samples;
|
|
UpdateImage();
|
|
}
|
|
|
|
|
|
///////////////////
|
|
// Update position
|
|
void AudioDisplay::UpdatePosition (int pos,bool IsSample) {
|
|
// Safeguards
|
|
if (!provider) return;
|
|
if (IsSample) pos /= samples;
|
|
int len = provider->GetNumSamples() / samples;
|
|
if (pos < 0) pos = 0;
|
|
if (pos >= len) pos = len-1;
|
|
|
|
// Set
|
|
Position = pos;
|
|
PositionSample = pos*samples;
|
|
UpdateScrollbar();
|
|
}
|
|
|
|
|
|
/////////////////////////////
|
|
// Set samples in percentage
|
|
// Note: aka Horizontal Zoom
|
|
void AudioDisplay::SetSamplesPercent(int percent,bool update,float pivot) {
|
|
// Calculate
|
|
if (percent < 1) percent = 1;
|
|
if (percent > 100) percent = 100;
|
|
if (samplesPercent == percent) return;
|
|
samplesPercent = percent;
|
|
|
|
// Update
|
|
if (update) {
|
|
// Center scroll
|
|
int oldSamples = samples;
|
|
UpdateSamples();
|
|
PositionSample += int64_t((oldSamples-samples)*w*pivot);
|
|
if (PositionSample < 0) PositionSample = 0;
|
|
|
|
// Update
|
|
UpdateSamples();
|
|
UpdateScrollbar();
|
|
UpdateImage();
|
|
Refresh(false);
|
|
}
|
|
}
|
|
|
|
|
|
//////////////////
|
|
// Update samples
|
|
void AudioDisplay::UpdateSamples() {
|
|
// Set samples
|
|
if (!provider) return;
|
|
if (w) {
|
|
int64_t totalSamples = provider->GetNumSamples();
|
|
int total = totalSamples / w;
|
|
int max = 5760000 / w; // 2 minutes at 48 kHz maximum
|
|
if (total > max) total = max;
|
|
int min = 8;
|
|
if (total < min) total = min;
|
|
int range = total-min;
|
|
samples = int(range*pow(samplesPercent/100.0,3)+min);
|
|
|
|
// Set position
|
|
int length = w * samples;
|
|
if (PositionSample + length > totalSamples) {
|
|
PositionSample = totalSamples - length;
|
|
if (PositionSample < 0) PositionSample = 0;
|
|
if (samples) Position = PositionSample / samples;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
/////////////
|
|
// Set scale
|
|
void AudioDisplay::SetScale(float _scale) {
|
|
if (scale == _scale) return;
|
|
scale = _scale;
|
|
UpdateImage();
|
|
}
|
|
|
|
|
|
//////////////////
|
|
// Load from file
|
|
void AudioDisplay::SetFile(wxString file) {
|
|
wxLogDebug(_T("AudioDisplay::SetFile(file=%s)"), file.c_str());
|
|
// Unload
|
|
if (file.IsEmpty()) try {
|
|
wxLogDebug(_T("AudioDisplay::SetFile: file is empty, just closing audio"));
|
|
try {
|
|
if (player) player->CloseStream();
|
|
}
|
|
catch (const wxChar *e) {
|
|
wxLogError(e);
|
|
}
|
|
delete provider;
|
|
delete player;
|
|
delete spectrumRenderer;
|
|
provider = NULL;
|
|
player = NULL;
|
|
spectrumRenderer = NULL;
|
|
try {
|
|
Reset();
|
|
}
|
|
catch (const wxChar *e) {
|
|
wxLogError(e);
|
|
}
|
|
|
|
loaded = false;
|
|
temporary = false;
|
|
StandardPaths::SetPathValue(_T("?audio"),_T(""));
|
|
}
|
|
catch (wxString e) {
|
|
wxLogError(e);
|
|
}
|
|
catch (const wxChar *e) {
|
|
wxLogError(e);
|
|
}
|
|
catch (...) {
|
|
wxLogError(_T("Unknown error unloading audio"));
|
|
}
|
|
|
|
// Load
|
|
else {
|
|
wxLogDebug(_T("AudioDisplay::SetFile: unloading old file"));
|
|
SetFile(_T(""));
|
|
try {
|
|
// Get provider
|
|
wxLogDebug(_T("AudioDisplay::SetFile: get audio provider"));
|
|
bool is_dummy = false;
|
|
#ifdef _DEBUG
|
|
if (file == _T("?dummy")) {
|
|
is_dummy = true;
|
|
provider = new DummyAudioProvider(150*60*1000, false); // 150 minutes non-noise
|
|
} else if (file == _T("?noise")) {
|
|
is_dummy = true;
|
|
provider = new DummyAudioProvider(150*60*1000, true); // 150 minutes noise
|
|
} else {
|
|
provider = AudioProviderFactoryManager::GetAudioProvider(file);
|
|
}
|
|
#else
|
|
provider = AudioProviderFactoryManager::GetAudioProvider(file);
|
|
#endif
|
|
|
|
// Get player
|
|
wxLogDebug(_T("AudioDisplay::SetFile: get audio player"));
|
|
player = AudioPlayerFactoryManager::GetAudioPlayer();
|
|
player->SetDisplayTimer(&UpdateTimer);
|
|
player->SetProvider(provider);
|
|
player->OpenStream();
|
|
loaded = true;
|
|
|
|
// Add to recent
|
|
if (!is_dummy) {
|
|
wxLogDebug(_T("AudioDisplay::SetFile: add to recent"));
|
|
Options.AddToRecentList(file,_T("Recent aud"));
|
|
wxFileName fn(file);
|
|
StandardPaths::SetPathValue(_T("?audio"),fn.GetPath());
|
|
}
|
|
|
|
// Update
|
|
UpdateImage();
|
|
}
|
|
catch (const wxChar *e) {
|
|
if (player) { delete player; player = 0; }
|
|
if (provider) { delete provider; provider = 0; }
|
|
wxLogError(e);
|
|
}
|
|
catch (wxString &err) {
|
|
if (player) { delete player; player = 0; }
|
|
if (provider) { delete provider; provider = 0; }
|
|
wxLogDebug(_T("AudioDisplay::SetFile: gotcha!"));
|
|
wxMessageBox(err,_T("Error loading audio"),wxICON_ERROR | wxOK);
|
|
}
|
|
catch (...) {
|
|
if (player) { delete player; player = 0; }
|
|
if (provider) { delete provider; provider = 0; }
|
|
wxLogError(_T("Unknown error loading audio"));
|
|
}
|
|
}
|
|
|
|
if (!loaded) return;
|
|
|
|
assert(loaded == (provider != NULL));
|
|
|
|
// Set default selection
|
|
wxLogDebug(_T("AudioDisplay::SetFile: set default selection"));
|
|
int n = grid->editBox->linen;
|
|
SetDialogue(grid,grid->GetDialogue(n),n);
|
|
wxLogDebug(_T("AudioDisplay::SetFile: returning"));
|
|
}
|
|
|
|
|
|
///////////////////
|
|
// Load from video
|
|
void AudioDisplay::SetFromVideo() {
|
|
wxLogDebug(_T("AudioDisplay::SetFromVideo"));
|
|
if (VideoContext::Get()->IsLoaded()) {
|
|
wxString extension = VideoContext::Get()->videoName.Right(4);
|
|
extension.LowerCase();
|
|
|
|
if (extension != _T(".d2v")) SetFile(VideoContext::Get()->videoName);
|
|
}
|
|
}
|
|
|
|
|
|
////////////////
|
|
// Reload audio
|
|
void AudioDisplay::Reload() {
|
|
wxLogDebug(_T("AudioDisplay::Reload"));
|
|
if (provider) SetFile(provider->GetFilename());
|
|
}
|
|
|
|
|
|
////////////////////
|
|
// Update scrollbar
|
|
void AudioDisplay::UpdateScrollbar() {
|
|
if (!provider) return;
|
|
int page = w/12;
|
|
int len = provider->GetNumSamples() / samples / 12;
|
|
Position = PositionSample / samples;
|
|
ScrollBar->SetScrollbar(Position/12,page,len,int(page*0.7),true);
|
|
}
|
|
|
|
|
|
//////////////////////////////////////////////
|
|
// Gets the sample number at the x coordinate
|
|
int64_t AudioDisplay::GetSampleAtX(int x) {
|
|
return (x+Position)*samples;
|
|
}
|
|
|
|
|
|
/////////////////////////////////////////////////
|
|
// Gets the x coordinate corresponding to sample
|
|
int AudioDisplay::GetXAtSample(int64_t n) {
|
|
return samples ? (n/samples)-Position : 0;
|
|
}
|
|
|
|
|
|
/////////////////
|
|
// Get MS from X
|
|
int AudioDisplay::GetMSAtX(int64_t x) {
|
|
return (PositionSample+(x*samples)) * 1000 / provider->GetSampleRate();
|
|
}
|
|
|
|
|
|
/////////////////
|
|
// Get X from MS
|
|
int AudioDisplay::GetXAtMS(int64_t ms) {
|
|
return ((ms * provider->GetSampleRate() / 1000)-PositionSample)/samples;
|
|
}
|
|
|
|
|
|
////////////////////
|
|
// Get MS At sample
|
|
int AudioDisplay::GetMSAtSample(int64_t x) {
|
|
return x * 1000 / provider->GetSampleRate();
|
|
}
|
|
|
|
|
|
////////////////////
|
|
// Get Sample at MS
|
|
int64_t AudioDisplay::GetSampleAtMS(int64_t ms) {
|
|
return ms * provider->GetSampleRate() / 1000;
|
|
}
|
|
|
|
|
|
////////
|
|
// Play
|
|
void AudioDisplay::Play(int start,int end) {
|
|
wxLogDebug(_T("AudioDisplay::Play"));
|
|
Stop();
|
|
|
|
// Check provider
|
|
if (!provider) {
|
|
wxLogDebug(_T("AudioDisplay::Play: no audio provider"));
|
|
// Load temporary provider from video
|
|
if (VideoContext::Get()->IsLoaded()) {
|
|
wxLogDebug(_T("AudioDisplay::Play: has video provider"));
|
|
try {
|
|
// Get provider
|
|
if (!VideoContext::Get()->videoName.StartsWith(_T("?dummy")))
|
|
provider = AudioProviderFactoryManager::GetAudioProvider(VideoContext::Get()->videoName, 0);
|
|
else
|
|
return;
|
|
|
|
// Get player
|
|
player = AudioPlayerFactoryManager::GetAudioPlayer();
|
|
player->SetDisplayTimer(&UpdateTimer);
|
|
player->SetProvider(provider);
|
|
player->OpenStream();
|
|
temporary = true;
|
|
wxLogDebug(_T("AudioDisplay::Play: got temp audio provider from video provider"));
|
|
}
|
|
catch (...) {
|
|
wxLogDebug(_T("AudioDisplay::Play: exception getting audio provider from video, returning"));
|
|
return;
|
|
}
|
|
}
|
|
if (!provider) {
|
|
wxLogDebug(_T("AudioDisplay::Play: has no provider, returning"));
|
|
return;
|
|
}
|
|
}
|
|
|
|
// Set defaults
|
|
wxLogDebug(_T("AudioDisplay::Play: initialising playback"));
|
|
int64_t num_samples = provider->GetNumSamples();
|
|
start = GetSampleAtMS(start);
|
|
if (end != -1) end = GetSampleAtMS(end);
|
|
else end = num_samples-1;
|
|
|
|
// Sanity checking
|
|
if (start < 0) start = 0;
|
|
if (start >= num_samples) start = num_samples-1;
|
|
if (end < 0) end = 0;
|
|
if (end >= num_samples) end = num_samples-1;
|
|
if (end < start) end = start;
|
|
|
|
// Call play
|
|
player->Play(start,end-start);
|
|
wxLogDebug(_T("AudioDisplay::Play: playback started, returning"));
|
|
}
|
|
|
|
|
|
////////
|
|
// Stop
|
|
void AudioDisplay::Stop() {
|
|
wxLogDebug(_T("AudioDisplay::Stop"));
|
|
if (VideoContext::Get()->IsPlaying()) VideoContext::Get()->Stop();
|
|
if (player) player->Stop();
|
|
}
|
|
|
|
|
|
///////////////////////////
|
|
// Get samples of dialogue
|
|
void AudioDisplay::GetTimesDialogue(int &start,int &end) {
|
|
wxLogDebug(_T("AudioDisplay::GetTimesDialogue"));
|
|
if (!dialogue) {
|
|
start = 0;
|
|
end = 0;
|
|
return;
|
|
}
|
|
|
|
start = dialogue->Start.GetMS();
|
|
end = dialogue->End.GetMS();
|
|
}
|
|
|
|
|
|
////////////////////////////
|
|
// Get samples of selection
|
|
void AudioDisplay::GetTimesSelection(int &start,int &end) {
|
|
wxLogDebug(_T("AudioDisplay::GetTimesSelection"));
|
|
start = 0;
|
|
end = 0;
|
|
if (!dialogue) return;
|
|
|
|
try {
|
|
if (karaoke->enabled) {
|
|
int pos = karaoke->syllables.at(karaoke->curSyllable).start_time;
|
|
int len = karaoke->syllables.at(karaoke->curSyllable).duration;
|
|
start = curStartMS+pos*10;
|
|
end = curStartMS+pos*10+len*10;
|
|
}
|
|
else {
|
|
start = curStartMS;
|
|
end = curEndMS;
|
|
}
|
|
}
|
|
catch (...) {}
|
|
}
|
|
|
|
|
|
/////////////////////////////
|
|
// Set the current selection
|
|
void AudioDisplay::SetSelection(int start, int end) {
|
|
wxLogDebug(_T("AudioDisplay::SetSelection(start=%d, end=%d)"), start, end);
|
|
curStartMS = start;
|
|
curEndMS = end;
|
|
Update();
|
|
}
|
|
|
|
|
|
////////////////
|
|
// Set dialogue
|
|
void AudioDisplay::SetDialogue(SubtitlesGrid *_grid,AssDialogue *diag,int n) {
|
|
wxLogDebug(_T("AudioDisplay::SetDialogue"));
|
|
// Actual parameters
|
|
if (_grid) {
|
|
wxLogDebug(_T("AudioDisplay::SetDialogue: has grid"));
|
|
// Set variables
|
|
grid = _grid;
|
|
line_n = n;
|
|
dialogue = diag;
|
|
|
|
// Set flags
|
|
diagUpdated = false;
|
|
NeedCommit = false;
|
|
|
|
// Set times
|
|
if (dialogue && !dontReadTimes && Options.AsBool(_T("Audio grab times on select"))) {
|
|
wxLogDebug(_T("AudioDisplay::SetDialogue: grabbing times"));
|
|
int s = dialogue->Start.GetMS();
|
|
int e = dialogue->End.GetMS();
|
|
|
|
// Never do it for 0:00:00.00->0:00:00.00 lines
|
|
if (s != 0 || e != 0) {
|
|
curStartMS = s;
|
|
curEndMS = e;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Read karaoke data
|
|
if (dialogue && karaoke->enabled) {
|
|
wxLogDebug(_T("AudioDisplay::SetDialogue: in karaoke mode, loading new line into karaoke control"));
|
|
NeedCommit = karaoke->LoadFromDialogue(dialogue);
|
|
|
|
// Reset karaoke pos
|
|
wxLogDebug(_T("AudioDisplay::SetDialogue: resetting karaoke position"));
|
|
if (karaoke->curSyllable == -1) karaoke->SetSyllable((int)karaoke->syllables.size()-1);
|
|
else karaoke->SetSyllable(0);
|
|
}
|
|
|
|
// Update
|
|
Update();
|
|
wxLogDebug(_T("AudioDisplay::SetDialogue: returning"));
|
|
}
|
|
|
|
|
|
//////////////////
|
|
// Commit changes
|
|
void AudioDisplay::CommitChanges (bool nextLine) {
|
|
wxLogDebug(_T("AudioDisplay::CommitChanges(nextLine=%d)"), nextLine?1:0);
|
|
// Loaded?
|
|
if (!loaded) return;
|
|
|
|
// Check validity
|
|
bool textNeedsCommit = grid->GetDialogue(line_n)->Text != grid->editBox->TextEdit->GetText();
|
|
bool timeNeedsCommit = grid->GetDialogue(line_n)->Start.GetMS() != curStartMS || grid->GetDialogue(line_n)->End.GetMS() != curEndMS;
|
|
if (timeNeedsCommit || textNeedsCommit) NeedCommit = true;
|
|
bool wasKaraSplitting = false;
|
|
bool validCommit = true;
|
|
if (!karaoke->enabled && !karaoke->splitting) {
|
|
if (!NeedCommit || curEndMS < curStartMS) validCommit = false;
|
|
}
|
|
|
|
// Update karaoke
|
|
int karaSelStart = 0, karaSelEnd = -1;
|
|
if (karaoke->enabled) {
|
|
wxLogDebug(_T("AudioDisplay::CommitChanges: karaoke enabled, committing it"));
|
|
wasKaraSplitting = karaoke->splitting;
|
|
karaoke->Commit();
|
|
// Get karaoke selection
|
|
karaSelStart = karaoke->syllables.size();
|
|
for (size_t k = 0; k < karaoke->syllables.size(); ++k) {
|
|
if (karaoke->syllables[k].selected) {
|
|
if ((signed)k < karaSelStart) karaSelStart = k;
|
|
if ((signed)k > karaSelEnd) karaSelEnd = k;
|
|
}
|
|
}
|
|
wxLogDebug(_T("AudioDisplay::CommitChanges: karaSelStart=%d karaSelEnd=%d"), karaSelStart, karaSelEnd);
|
|
}
|
|
|
|
// Get selected rows
|
|
wxArrayInt sel = grid->GetSelection();
|
|
|
|
// Commit ok?
|
|
if (validCommit) {
|
|
wxLogDebug(_T("AudioDisplay::CommitChanges: valid commit"));
|
|
// Reset flags
|
|
diagUpdated = false;
|
|
NeedCommit = false;
|
|
|
|
// Update dialogues
|
|
blockUpdate = true;
|
|
AssDialogue *curDiag;
|
|
for (size_t i=0;i<sel.GetCount();i++) {
|
|
if (grid->IsInSelection(line_n)) curDiag = grid->GetDialogue(sel[i]);
|
|
else curDiag = grid->GetDialogue(line_n);
|
|
if (timeNeedsCommit) {
|
|
curDiag->Start.SetMS(curStartMS);
|
|
curDiag->End.SetMS(curEndMS);
|
|
}
|
|
if (!karaoke->enabled && textNeedsCommit) {
|
|
// If user was editing karaoke stuff, that should take precedence of manual changes in the editbox,
|
|
// so only update from editbox when not in kara mode
|
|
curDiag->Text = grid->editBox->TextEdit->GetText();
|
|
}
|
|
curDiag->UpdateData();
|
|
if (!grid->IsInSelection(line_n)) break;
|
|
}
|
|
|
|
// Update edit box
|
|
wxLogDebug(_T("AudioDisplay::CommitChanges: updating time edit boxes"));
|
|
grid->editBox->StartTime->Update();
|
|
grid->editBox->EndTime->Update();
|
|
grid->editBox->Duration->Update();
|
|
|
|
// Update grid
|
|
wxLogDebug(_T("AudioDisplay::CommitChanges: update grid"));
|
|
grid->editBox->Update(!karaoke->enabled);
|
|
grid->ass->FlagAsModified(_T(""));
|
|
grid->CommitChanges();
|
|
karaoke->SetSelection(karaSelStart, karaSelEnd);
|
|
blockUpdate = false;
|
|
}
|
|
|
|
// Next line (ugh what a condition, can this be simplified?)
|
|
if (nextLine && !karaoke->enabled && Options.AsBool(_T("Audio Next Line on Commit")) && !wasKaraSplitting) {
|
|
wxLogDebug(_T("AudioDisplay::CommitChanges: going to next line"));
|
|
// Insert a line if it doesn't exist
|
|
int nrows = grid->GetRows();
|
|
if (nrows == line_n + 1) {
|
|
wxLogDebug(_T("AudioDisplay::CommitChanges: was on last line, inserting new"));
|
|
AssDialogue *def = new AssDialogue;
|
|
def->Start = grid->GetDialogue(line_n)->End;
|
|
def->End = grid->GetDialogue(line_n)->End;
|
|
def->End.SetMS(def->End.GetMS()+Options.AsInt(_T("Timing Default Duration")));
|
|
def->Style = grid->GetDialogue(line_n)->Style;
|
|
grid->InsertLine(def,line_n,true);
|
|
curStartMS = curEndMS;
|
|
curEndMS = curStartMS + Options.AsInt(_T("Timing Default Duration"));
|
|
}
|
|
else if (grid->GetDialogue(line_n+1)->Start.GetMS() == 0 && grid->GetDialogue(line_n+1)->End.GetMS() == 0) {
|
|
curStartMS = curEndMS;
|
|
curEndMS = curStartMS + Options.AsInt(_T("Timing Default Duration"));
|
|
}
|
|
else {
|
|
curStartMS = grid->GetDialogue(line_n+1)->Start.GetMS();
|
|
curEndMS = grid->GetDialogue(line_n+1)->End.GetMS();
|
|
}
|
|
|
|
// Go to next
|
|
dontReadTimes = true;
|
|
ChangeLine(1,sel.GetCount() > 1 ? true : false);
|
|
dontReadTimes = false;
|
|
}
|
|
|
|
Update();
|
|
wxLogDebug(_T("AudioDisplay::CommitChanges: returning"));
|
|
}
|
|
|
|
|
|
////////////
|
|
// Add lead
|
|
void AudioDisplay::AddLead(bool in,bool out) {
|
|
// Lead in
|
|
if (in) {
|
|
curStartMS -= Options.AsInt(_T("Audio Lead in"));
|
|
if (curStartMS < 0) curStartMS = 0;
|
|
}
|
|
|
|
// Lead out
|
|
if (out) {
|
|
curEndMS += Options.AsInt(_T("Audio Lead out"));
|
|
}
|
|
|
|
// Set changes
|
|
UpdateTimeEditCtrls();
|
|
NeedCommit = true;
|
|
if (Options.AsBool(_T("Audio Autocommit"))) CommitChanges();
|
|
Update();
|
|
}
|
|
|
|
|
|
///////////////
|
|
// Event table
|
|
BEGIN_EVENT_TABLE(AudioDisplay, wxWindow)
|
|
EVT_MOUSE_EVENTS(AudioDisplay::OnMouseEvent)
|
|
EVT_PAINT(AudioDisplay::OnPaint)
|
|
EVT_SIZE(AudioDisplay::OnSize)
|
|
EVT_TIMER(Audio_Update_Timer,AudioDisplay::OnUpdateTimer)
|
|
EVT_KEY_DOWN(AudioDisplay::OnKeyDown)
|
|
EVT_SET_FOCUS(AudioDisplay::OnGetFocus)
|
|
EVT_KILL_FOCUS(AudioDisplay::OnLoseFocus)
|
|
END_EVENT_TABLE()
|
|
|
|
|
|
/////////
|
|
// Paint
|
|
void AudioDisplay::OnPaint(wxPaintEvent& event) {
|
|
if (w == 0 || h == 0) return;
|
|
DoUpdateImage();
|
|
|
|
wxPaintDC dc(this);
|
|
dc.DrawBitmap(*origImage,0,0);
|
|
}
|
|
|
|
|
|
///////////////
|
|
// Mouse event
|
|
void AudioDisplay::OnMouseEvent(wxMouseEvent& event) {
|
|
// Get x,y
|
|
int64_t x = event.GetX();
|
|
int64_t y = event.GetY();
|
|
bool karMode = karaoke->enabled;
|
|
bool shiftDown = event.m_shiftDown;
|
|
int timelineHeight = Options.AsBool(_T("Audio Draw Timeline")) ? 20 : 0;
|
|
|
|
// Leaving event
|
|
if (event.Leaving()) {
|
|
event.Skip();
|
|
return;
|
|
}
|
|
|
|
// Is inside?
|
|
bool inside = false;
|
|
bool onScale = false;
|
|
if (x >= 0 && y >= 0 && x < w) {
|
|
if (y < h) {
|
|
inside = true;
|
|
|
|
// Get focus
|
|
if (wxWindow::FindFocus() != this && Options.AsBool(_T("Audio Autofocus"))) SetFocus();
|
|
}
|
|
else if (y < h+timelineHeight) onScale = true;
|
|
}
|
|
|
|
// Buttons
|
|
bool leftIsDown = event.ButtonIsDown(wxMOUSE_BTN_LEFT);
|
|
bool rightIsDown = event.ButtonIsDown(wxMOUSE_BTN_RIGHT);
|
|
bool buttonIsDown = leftIsDown || rightIsDown;
|
|
bool leftClick = event.ButtonDown(wxMOUSE_BTN_LEFT);
|
|
bool rightClick = event.ButtonDown(wxMOUSE_BTN_RIGHT);
|
|
bool middleClick = event.Button(wxMOUSE_BTN_MIDDLE);
|
|
bool buttonClick = leftClick || rightClick;
|
|
bool defCursor = true;
|
|
|
|
// Click type
|
|
if (buttonClick && !holding) {
|
|
holding = true;
|
|
CaptureMouse();
|
|
}
|
|
if (!buttonIsDown && holding) {
|
|
holding = false;
|
|
if (HasCapture()) ReleaseMouse();
|
|
}
|
|
|
|
// Mouse wheel
|
|
if (event.GetWheelRotation() != 0) {
|
|
// Zoom or scroll?
|
|
bool zoom = shiftDown;
|
|
if (Options.AsBool(_T("Audio Wheel Default To Zoom"))) zoom = !zoom;
|
|
|
|
// Zoom
|
|
if (zoom) {
|
|
#ifdef __APPLE__
|
|
// Reverse scroll directions on Apple... ugly hack
|
|
// Otherwise left=right and right=left on systems that support four-way scrolling.
|
|
int step = -event.GetWheelRotation() / event.GetWheelDelta();
|
|
#else
|
|
int step = event.GetWheelRotation() / event.GetWheelDelta();
|
|
#endif
|
|
int value = box->HorizontalZoom->GetValue()+step;
|
|
box->HorizontalZoom->SetValue(value);
|
|
SetSamplesPercent(value,true,float(x)/float(w));
|
|
}
|
|
|
|
// Scroll
|
|
else {
|
|
int step = -event.GetWheelRotation() * w / 360;
|
|
UpdatePosition(Position+step,false);
|
|
UpdateImage();
|
|
}
|
|
}
|
|
|
|
// Cursor drawing
|
|
if (!player->IsPlaying() && origImage) {
|
|
// Draw bg
|
|
wxClientDC dc(this);
|
|
dc.DrawBitmap(*origImage,0,0);
|
|
|
|
if (inside) {
|
|
// Draw cursor
|
|
dc.SetLogicalFunction(wxINVERT);
|
|
dc.DrawLine(x,0,x,h);
|
|
|
|
// Time
|
|
if (Options.AsBool(_T("Audio Draw Cursor Time"))) {
|
|
// Time string
|
|
AssTime time;
|
|
time.SetMS(GetMSAtX(x));
|
|
wxString text = time.GetASSFormated();
|
|
|
|
// Calculate metrics
|
|
wxFont font(10,wxFONTFAMILY_DEFAULT,wxFONTSTYLE_NORMAL,wxFONTWEIGHT_BOLD,false,_T("Verdana"));
|
|
dc.SetFont(font);
|
|
int tw,th;
|
|
GetTextExtent(text,&tw,&th,NULL,NULL,&font);
|
|
|
|
// Set inversion
|
|
bool left = true;
|
|
if (x > w/2) left = false;
|
|
|
|
// Text coordinates
|
|
int dx;
|
|
dx = x - tw/2;
|
|
if (dx < 4) dx = 4;
|
|
int max = w - tw - 4;
|
|
if (dx > max) dx = max;
|
|
int dy = 4;
|
|
if (karMode) dy += th;
|
|
|
|
// Draw text
|
|
dc.SetTextForeground(wxColour(64,64,64));
|
|
dc.DrawText(text,dx+1,dy-1);
|
|
dc.DrawText(text,dx+1,dy+1);
|
|
dc.DrawText(text,dx-1,dy-1);
|
|
dc.DrawText(text,dx-1,dy+1);
|
|
dc.SetTextForeground(wxColour(255,255,255));
|
|
dc.DrawText(text,dx,dy);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Scale dragging
|
|
if ((hold == 0 && onScale) || draggingScale) {
|
|
if (event.ButtonDown(wxMOUSE_BTN_LEFT)) {
|
|
lastDragX = x;
|
|
draggingScale = true;
|
|
}
|
|
else if (holding) {
|
|
int delta = lastDragX - x;
|
|
lastDragX = x;
|
|
UpdatePosition(Position + delta);
|
|
UpdateImage();
|
|
Refresh(false);
|
|
SetCursor(wxNullCursor);
|
|
return;
|
|
}
|
|
else draggingScale = false;
|
|
}
|
|
|
|
// Outside
|
|
if (!inside && hold == 0) return;
|
|
|
|
// Left click
|
|
if (leftClick) {
|
|
SetFocus();
|
|
}
|
|
|
|
// Right click
|
|
if (rightClick) {
|
|
SetFocus();
|
|
if (karaoke->enabled) {
|
|
int syl = GetSyllableAtX(x);
|
|
if (syl != -1) {
|
|
int start = karaoke->syllables.at(syl).start_time * 10 + dialogue->Start.GetMS();
|
|
int count = karaoke->syllables.at(syl).duration * 10;
|
|
player->Play(GetSampleAtMS(start),GetSampleAtMS(count));
|
|
//return;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Middle click
|
|
if (middleClick) {
|
|
SetFocus();
|
|
if (VideoContext::Get()->IsLoaded()) {
|
|
VideoContext::Get()->JumpToTime(GetMSAtX(x),true);
|
|
}
|
|
}
|
|
|
|
// Timing
|
|
if (hasSel) {
|
|
bool updated = false;
|
|
|
|
// Grab start/end
|
|
if (hold == 0) {
|
|
bool gotGrab = false;
|
|
bool karTime = karMode && !
|
|
#ifdef __APPLE__
|
|
event.CmdDown();
|
|
#else
|
|
event.ControlDown();
|
|
#endif
|
|
|
|
// Line timing mode
|
|
if (!karTime) {
|
|
// Grab start
|
|
if (abs64 (x - selStart) < 6 && Options.AsBool(_T("Disable Dragging Times"))==false) {
|
|
wxCursor cursor(wxCURSOR_SIZEWE);
|
|
SetCursor(cursor);
|
|
defCursor = false;
|
|
if (buttonClick) {
|
|
hold = 1;
|
|
gotGrab = true;
|
|
}
|
|
}
|
|
|
|
// Grab end
|
|
else if (abs64 (x - selEnd) < 6 && Options.AsBool(_T("Disable Dragging Times"))==false) {
|
|
wxCursor cursor(wxCURSOR_SIZEWE);
|
|
SetCursor(cursor);
|
|
defCursor = false;
|
|
if (buttonClick) {
|
|
hold = 2;
|
|
gotGrab = true;
|
|
}
|
|
}
|
|
|
|
// Dragging nothing, time from scratch
|
|
else {
|
|
if (buttonClick) {
|
|
if (leftClick) hold = 3;
|
|
else hold = 2;
|
|
lastX = x;
|
|
gotGrab = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Karaoke mode
|
|
else {
|
|
// Look for a syllable
|
|
int64_t pos,len,curpos;
|
|
AudioKaraokeSyllable *curSyl;
|
|
size_t karn = karaoke->syllables.size();
|
|
for (size_t i=0;i<karn;i++) {
|
|
curSyl = &karaoke->syllables.at(i);
|
|
len = curSyl->duration*10;
|
|
curpos = curSyl->start_time*10;
|
|
if (len != -1) {
|
|
pos = GetXAtMS(curStartMS+len+curpos);
|
|
|
|
// Grabbing syllable boundary
|
|
if (abs64 (x - pos) < 4) {
|
|
wxCursor cursor(wxCURSOR_SIZEWE);
|
|
SetCursor(cursor);
|
|
defCursor = false;
|
|
if (event.LeftIsDown()) {
|
|
hold = 4;
|
|
holdSyl = (int)i;
|
|
gotGrab = true;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
// No syllable found, select if possible
|
|
if (hold == 0 && leftClick) {
|
|
int syl = GetSyllableAtX(x);
|
|
if (syl != -1) {
|
|
karaoke->SetSyllable(syl);
|
|
updated = true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Drag start/end
|
|
if (hold != 0) {
|
|
// Dragging
|
|
if (buttonIsDown) {
|
|
// Drag from nothing or straight timing
|
|
if (hold == 3 && buttonIsDown) {
|
|
if (!karMode) {
|
|
if (leftIsDown) curStartMS = GetMSAtX(x);
|
|
else curEndMS = GetMSAtX(x);
|
|
updated = true;
|
|
diagUpdated = true;
|
|
|
|
if (leftIsDown && abs((long)(x-lastX)) > Options.AsInt(_T("Audio Start Drag Sensitivity"))) {
|
|
selStart = lastX;
|
|
selEnd = x;
|
|
curStartMS = GetBoundarySnap(GetMSAtX(lastX),10,event.ShiftDown(),true);
|
|
curEndMS = GetMSAtX(x);
|
|
hold = 2;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Drag start
|
|
if (hold == 1 && buttonIsDown) {
|
|
// Set new value
|
|
if (x != selStart) {
|
|
int snapped = GetBoundarySnap(GetMSAtX(x),10,event.ShiftDown(),true);
|
|
selStart = GetXAtMS(snapped);
|
|
if (selStart > selEnd) {
|
|
int temp = selStart;
|
|
selStart = selEnd;
|
|
selEnd = temp;
|
|
hold = 2;
|
|
curEndMS = snapped;
|
|
snapped = GetMSAtX(selStart);
|
|
}
|
|
curStartMS = snapped;
|
|
updated = true;
|
|
diagUpdated = true;
|
|
}
|
|
}
|
|
|
|
// Drag end
|
|
if (hold == 2 && buttonIsDown) {
|
|
// Set new value
|
|
if (x != selEnd) {
|
|
int snapped = GetBoundarySnap(GetMSAtX(x),10,event.ShiftDown(),false);
|
|
selEnd = GetXAtMS(snapped);
|
|
//selEnd = GetBoundarySnap(x,event.ShiftDown()?0:10,false);
|
|
if (selStart > selEnd) {
|
|
int temp = selStart;
|
|
selStart = selEnd;
|
|
selEnd = temp;
|
|
hold = 1;
|
|
curStartMS = snapped;
|
|
snapped = GetMSAtX(selEnd);
|
|
}
|
|
curEndMS = snapped;
|
|
updated = true;
|
|
diagUpdated = true;
|
|
}
|
|
}
|
|
|
|
// Drag karaoke
|
|
if (hold == 4 && leftIsDown) {
|
|
// Set new value
|
|
int curpos,len,pos,nkar;
|
|
AudioKaraokeSyllable *curSyl=NULL,*nextSyl=NULL;
|
|
curSyl = &karaoke->syllables.at(holdSyl);
|
|
nkar = (int)karaoke->syllables.size();
|
|
if (holdSyl < nkar-1) {
|
|
nextSyl = &karaoke->syllables.at(holdSyl+1);
|
|
}
|
|
curpos = curSyl->start_time;
|
|
len = curSyl->duration;
|
|
pos = GetXAtMS(curStartMS+(len+curpos)*10);
|
|
if (x != pos) {
|
|
// Calculate delta in centiseconds
|
|
int delta = ((int64_t)(x-pos)*samples*100)/provider->GetSampleRate();
|
|
|
|
// Apply delta
|
|
int deltaMode = 0;
|
|
if (shiftDown) deltaMode = 1;
|
|
// else if (ctrlDown) deltaMode = 2;
|
|
bool result = karaoke->SyllableDelta(holdSyl,delta,deltaMode);
|
|
if (result) {
|
|
updated = true;
|
|
diagUpdated = true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Release
|
|
else {
|
|
// Commit changes
|
|
if (diagUpdated) {
|
|
diagUpdated = false;
|
|
NeedCommit = true;
|
|
if (curStartMS <= curEndMS) {
|
|
UpdateTimeEditCtrls();
|
|
if (Options.AsBool(_T("Audio Autocommit"))) CommitChanges();
|
|
}
|
|
|
|
else UpdateImage(true);
|
|
}
|
|
|
|
// Update stuff
|
|
SetCursor(wxNullCursor);
|
|
hold = 0;
|
|
}
|
|
}
|
|
|
|
// Update stuff
|
|
if (updated) {
|
|
if (diagUpdated) NeedCommit = true;
|
|
if (karaoke->enabled) {
|
|
AudioKaraokeSyllable &syl = karaoke->syllables[karaoke->curSyllable];
|
|
player->SetEndPosition(GetSampleAtMS(curStartMS + (syl.start_time+syl.duration)*10));
|
|
} else {
|
|
player->SetEndPosition(GetSampleAtX(selEnd));
|
|
}
|
|
if (hold != 0) {
|
|
wxCursor cursor(wxCURSOR_SIZEWE);
|
|
SetCursor(cursor);
|
|
}
|
|
UpdateImage(true);
|
|
}
|
|
}
|
|
|
|
// Not holding
|
|
else {
|
|
hold = 0;
|
|
}
|
|
|
|
// Restore cursor
|
|
if (defCursor) SetCursor(wxNullCursor);
|
|
}
|
|
|
|
|
|
////////////////////////
|
|
// Get snap to boundary
|
|
int AudioDisplay::GetBoundarySnap(int ms,int rangeX,bool shiftHeld,bool start) {
|
|
// Range?
|
|
if (rangeX <= 0) return ms;
|
|
|
|
// Convert range into miliseconds
|
|
int rangeMS = rangeX*samples*1000 / provider->GetSampleRate();
|
|
|
|
// Keyframe boundaries
|
|
wxArrayInt boundaries;
|
|
bool snapKey = Options.AsBool(_T("Audio snap to keyframes"));
|
|
if (shiftHeld) snapKey = !snapKey;
|
|
if (snapKey && VideoContext::Get()->KeyFramesLoaded() && Options.AsBool(_T("Audio Draw Keyframes"))) {
|
|
int64_t keyMS;
|
|
wxArrayInt keyFrames = VideoContext::Get()->GetKeyFrames();
|
|
int frame;
|
|
for (unsigned int i=0;i<keyFrames.Count();i++) {
|
|
frame = keyFrames[i];
|
|
if (!start) frame--;
|
|
if (frame < 0) frame = 0;
|
|
keyMS = VFR_Output.GetTimeAtFrame(frame,start);
|
|
//if (start) keyX++;
|
|
if (GetXAtMS(keyMS) >= 0 && GetXAtMS(keyMS) < w) boundaries.Add(keyMS);
|
|
}
|
|
}
|
|
|
|
// Other subtitles' boundaries
|
|
int inactiveType = Options.AsInt(_T("Audio Inactive Lines Display Mode"));
|
|
bool snapLines = Options.AsBool(_T("Audio snap to other lines"));
|
|
if (shiftHeld) snapLines = !snapLines;
|
|
if (snapLines && (inactiveType == 1 || inactiveType == 2)) {
|
|
AssDialogue *shade;
|
|
int shadeX1,shadeX2;
|
|
int shadeFrom,shadeTo;
|
|
|
|
// Get range
|
|
if (inactiveType == 1) {
|
|
shadeFrom = this->line_n-1;
|
|
shadeTo = shadeFrom+1;
|
|
}
|
|
else {
|
|
shadeFrom = 0;
|
|
shadeTo = grid->GetRows();
|
|
}
|
|
|
|
for (int j=shadeFrom;j<shadeTo;j++) {
|
|
if (j == line_n) continue;
|
|
shade = grid->GetDialogue(j);
|
|
|
|
if (shade) {
|
|
// Get coordinates
|
|
shadeX1 = GetXAtMS(shade->Start.GetMS());
|
|
shadeX2 = GetXAtMS(shade->End.GetMS());
|
|
if (shadeX1 >= 0 && shadeX1 < w) boundaries.Add(shade->Start.GetMS());
|
|
if (shadeX2 >= 0 && shadeX2 < w) boundaries.Add(shade->End.GetMS());
|
|
}
|
|
}
|
|
}
|
|
|
|
// See if ms falls within range of any of them
|
|
int minDist = rangeMS+1;
|
|
int bestMS = ms;
|
|
for (unsigned int i=0;i<boundaries.Count();i++) {
|
|
if (abs(ms-boundaries[i]) < minDist) {
|
|
bestMS = boundaries[i];
|
|
minDist = abs(ms-boundaries[i]);
|
|
}
|
|
}
|
|
|
|
// Return best match
|
|
return bestMS;
|
|
}
|
|
|
|
|
|
//
|
|
// SCRUBBING CODE, REMOVED FROM THE FUNCTION ABOVE
|
|
/*
|
|
// Stop scrubbing
|
|
bool scrubButton = false && event.ButtonIsDown(wxMOUSE_BTN_MIDDLE);
|
|
if (scrubbing && !scrubButton) {
|
|
// Release mouse
|
|
scrubbing = false;
|
|
if (HasCapture()) ReleaseMouse();
|
|
|
|
// Stop player
|
|
player->Stop();
|
|
player->SetProvider(provider);
|
|
delete scrubProvider;
|
|
}
|
|
|
|
// Start scrubbing
|
|
if (!scrubbing && scrubButton && provider->GetChannels() == 1) {
|
|
// Get mouse
|
|
CaptureMouse();
|
|
scrubbing = true;
|
|
|
|
// Initialize provider
|
|
player->Stop();
|
|
scrubProvider = new StreamAudioProvider();
|
|
scrubProvider->SetParams(provider->GetChannels(),provider->GetSampleRate(),provider->GetBytesPerSample());
|
|
player->SetProvider(scrubProvider);
|
|
|
|
// Set variables
|
|
scrubLastPos = GetSampleAtX(x);
|
|
scrubTime = clock();
|
|
scrubLastRate = provider->GetSampleRate();
|
|
}
|
|
|
|
// Scrub
|
|
if (scrubbing && scrubButton) {
|
|
// Get current data
|
|
int64_t exactPos = MAX(0,GetSampleAtX(x));
|
|
int curScrubTime = clock();
|
|
int scrubDeltaTime = curScrubTime - scrubTime;
|
|
bool invert = exactPos < scrubLastPos;
|
|
int64_t curScrubPos = exactPos;
|
|
|
|
if (scrubDeltaTime > 0) {
|
|
// Get derived data
|
|
int rateChange = provider->GetSampleRate()/20;
|
|
int curRate = MID(int(scrubLastRate-rateChange),abs(int(exactPos - scrubLastPos)) * CLOCKS_PER_SEC / scrubDeltaTime,int(scrubLastRate+rateChange));
|
|
if (abs(curRate-scrubLastRate) < rateChange) curRate = scrubLastRate;
|
|
curScrubPos = scrubLastPos + (curRate * scrubDeltaTime / CLOCKS_PER_SEC * (invert ? -1 : 1));
|
|
int64_t scrubDelta = curScrubPos - scrubLastPos;
|
|
scrubLastRate = curRate;
|
|
|
|
// Copy data to buffer
|
|
if (scrubDelta != 0) {
|
|
// Create buffer
|
|
int bufSize = scrubDeltaTime * scrubProvider->GetSampleRate() / CLOCKS_PER_SEC;
|
|
short *buf = new short[bufSize];
|
|
|
|
// Flag as inverted, if necessary
|
|
if (invert) scrubDelta = -scrubDelta;
|
|
|
|
// Copy data from original provider to temp buffer
|
|
short *temp = new short[scrubDelta];
|
|
provider->GetAudio(temp,MIN(curScrubPos,scrubLastPos),scrubDelta);
|
|
|
|
// Scale
|
|
float scale = float(double(scrubDelta) / double(bufSize));
|
|
float start,end;
|
|
int istart,iend;
|
|
float tempfinal;
|
|
for (int i=0;i<bufSize;i++) {
|
|
start = i*scale;
|
|
end = (i+1)*scale;
|
|
istart = (int) start;
|
|
iend = MIN((int) end,scrubDelta-1);
|
|
if (istart == iend) tempfinal = temp[istart] * (end - start);
|
|
else {
|
|
tempfinal = temp[istart] * (1 + istart - start) + temp[iend] * (end - iend);
|
|
for (int j=istart+1;j<iend;j++) tempfinal += temp[i];
|
|
}
|
|
buf[i] = tempfinal / scale;
|
|
}
|
|
//int len = MIN(bufSize,scrubDelta);
|
|
//for (int i=0;i<len;i++) buf[i] = temp[i];
|
|
//for (int i=len;i<bufSize;i++) buf[i] = 0;
|
|
delete temp;
|
|
|
|
// Invert
|
|
if (invert) {
|
|
short aux;
|
|
for (int i=0;i<bufSize/2;i++) {
|
|
aux = buf[i];
|
|
buf[i] = buf[bufSize-i-1];
|
|
buf[bufSize-i-1] = aux;
|
|
}
|
|
}
|
|
|
|
// Send data to provider
|
|
scrubProvider->Append(buf,bufSize);
|
|
if (!player->IsPlaying()) player->Play(0,~0ULL);
|
|
delete buf;
|
|
}
|
|
}
|
|
|
|
// Update last pos and time
|
|
scrubLastPos = curScrubPos;
|
|
scrubTime = curScrubTime;
|
|
|
|
// Return
|
|
return;
|
|
}
|
|
|
|
*/
|
|
|
|
|
|
//////////////
|
|
// Size event
|
|
void AudioDisplay::OnSize(wxSizeEvent &event) {
|
|
// Set size
|
|
GetClientSize(&w,&h);
|
|
h -= Options.AsBool(_T("Audio Draw Timeline")) ? 20 : 0;
|
|
|
|
// Update image
|
|
UpdateSamples();
|
|
if (samples) {
|
|
UpdatePosition(PositionSample / samples);
|
|
}
|
|
UpdateImage();
|
|
|
|
// Update scrollbar
|
|
UpdateScrollbar();
|
|
}
|
|
|
|
|
|
///////////////
|
|
// Timer event
|
|
void AudioDisplay::OnUpdateTimer(wxTimerEvent &event) {
|
|
if (!origImage)
|
|
return;
|
|
|
|
// Get lock and check if it's OK
|
|
if (player->GetMutex()) {
|
|
wxMutexLocker locker(*player->GetMutex());
|
|
if (!locker.IsOk()) return;
|
|
}
|
|
|
|
if (!player->IsPlaying()) return;
|
|
|
|
// Get DCs
|
|
//wxMutexGuiEnter();
|
|
wxClientDC dc(this);
|
|
|
|
// Draw cursor
|
|
int curpos = -1;
|
|
if (player->IsPlaying()) {
|
|
int64_t curPos = player->GetCurrentPosition();
|
|
if (curPos > player->GetStartPosition() && curPos < player->GetEndPosition()) {
|
|
// Scroll if needed
|
|
int posX = GetXAtSample(curPos);
|
|
bool fullDraw = false;
|
|
bool centerLock = false;
|
|
bool scrollToCursor = Options.AsBool(_T("Audio lock scroll on cursor"));
|
|
if (centerLock) {
|
|
int goTo = MAX(0,curPos - w*samples/2);
|
|
if (goTo >= 0) {
|
|
UpdatePosition(goTo,true);
|
|
UpdateImage();
|
|
fullDraw = true;
|
|
}
|
|
}
|
|
else {
|
|
if (scrollToCursor) {
|
|
if (posX < 80 || posX > w-80) {
|
|
int goTo = MAX(0,curPos - 80*samples);
|
|
if (goTo >= 0) {
|
|
UpdatePosition(goTo,true);
|
|
UpdateImage();
|
|
fullDraw = true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Draw cursor
|
|
wxMemoryDC src;
|
|
curpos = GetXAtSample(curPos);
|
|
if (curpos >= 0 && curpos < GetClientSize().GetWidth()) {
|
|
dc.SetPen(wxPen(Options.AsColour(_T("Audio Play cursor"))));
|
|
src.SelectObject(*origImage);
|
|
if (fullDraw) {
|
|
//dc.Blit(0,0,w,h,&src,0,0);
|
|
dc.DrawLine(curpos,0,curpos,h);
|
|
//dc.Blit(0,0,curpos-10,h,&src,0,0);
|
|
//dc.Blit(curpos+10,0,w-curpos-10,h,&src,curpos+10,0);
|
|
}
|
|
else {
|
|
dc.Blit(oldCurPos,0,1,h,&src,oldCurPos,0);
|
|
dc.DrawLine(curpos,0,curpos,h);
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
if (curPos > player->GetEndPosition() + 8192) {
|
|
player->Stop();
|
|
}
|
|
wxMemoryDC src;
|
|
src.SelectObject(*origImage);
|
|
dc.Blit(oldCurPos,0,1,h,&src,oldCurPos,0);
|
|
}
|
|
}
|
|
|
|
// Restore background
|
|
else {
|
|
wxMemoryDC src;
|
|
src.SelectObject(*origImage);
|
|
dc.Blit(oldCurPos,0,1,h,&src,oldCurPos,0);
|
|
}
|
|
oldCurPos = curpos;
|
|
}
|
|
|
|
|
|
////////////
|
|
// Key down
|
|
void AudioDisplay::OnKeyDown(wxKeyEvent &event) {
|
|
int key = event.GetKeyCode();
|
|
#ifdef __APPLE__
|
|
Hotkeys.SetPressed(key,event.m_metaDown,event.m_altDown,event.m_shiftDown);
|
|
#else
|
|
Hotkeys.SetPressed(key,event.m_controlDown,event.m_altDown,event.m_shiftDown);
|
|
#endif
|
|
|
|
// Accept
|
|
if (Hotkeys.IsPressed(_T("Audio Commit"))) {
|
|
CommitChanges(true);
|
|
//ChangeLine(1);
|
|
}
|
|
|
|
// Accept (SSA's "Grab times")
|
|
if (Hotkeys.IsPressed(_T("Audio Commit Alt"))) {
|
|
CommitChanges(true);
|
|
}
|
|
|
|
// Accept (stay)
|
|
if (Hotkeys.IsPressed(_T("Audio Commit (Stay)"))) {
|
|
CommitChanges();
|
|
}
|
|
|
|
// Previous
|
|
if (Hotkeys.IsPressed(_T("Audio Prev Line")) || Hotkeys.IsPressed(_T("Audio Prev Line Alt"))) {
|
|
Prev();
|
|
}
|
|
|
|
// Next
|
|
if (Hotkeys.IsPressed(_T("Audio Next Line")) || Hotkeys.IsPressed(_T("Audio Next Line Alt"))) {
|
|
Next();
|
|
}
|
|
|
|
// Play
|
|
if (Hotkeys.IsPressed(_T("Audio Play")) || Hotkeys.IsPressed(_T("Audio Play Alt"))) {
|
|
int start=0,end=0;
|
|
GetTimesSelection(start,end);
|
|
Play(start,end);
|
|
}
|
|
|
|
// Play/Stop
|
|
if (Hotkeys.IsPressed(_T("Audio Play or Stop"))) {
|
|
if (player->IsPlaying()) Stop();
|
|
else {
|
|
int start=0,end=0;
|
|
GetTimesSelection(start,end);
|
|
Play(start,end);
|
|
}
|
|
}
|
|
|
|
// Stop
|
|
if (Hotkeys.IsPressed(_T("Audio Stop"))) {
|
|
Stop();
|
|
}
|
|
|
|
// Increase length
|
|
if (Hotkeys.IsPressed(_T("Audio Karaoke Increase Len"))) {
|
|
if (karaoke->enabled) {
|
|
bool result = karaoke->SyllableDelta(karaoke->curSyllable,1,0);
|
|
if (result) diagUpdated = true;
|
|
}
|
|
}
|
|
|
|
// Increase length (shift)
|
|
if (Hotkeys.IsPressed(_T("Audio Karaoke Increase Len Shift"))) {
|
|
if (karaoke->enabled) {
|
|
bool result = karaoke->SyllableDelta(karaoke->curSyllable,1,1);
|
|
if (result) diagUpdated = true;
|
|
}
|
|
}
|
|
|
|
// Decrease length
|
|
if (Hotkeys.IsPressed(_T("Audio Karaoke Decrease Len"))) {
|
|
if (karaoke->enabled) {
|
|
bool result = karaoke->SyllableDelta(karaoke->curSyllable,-1,0);
|
|
if (result) diagUpdated = true;
|
|
}
|
|
}
|
|
|
|
// Decrease length (shift)
|
|
if (Hotkeys.IsPressed(_T("Audio Karaoke Decrease Len Shift"))) {
|
|
if (karaoke->enabled) {
|
|
bool result = karaoke->SyllableDelta(karaoke->curSyllable,-1,1);
|
|
if (result) diagUpdated = true;
|
|
}
|
|
}
|
|
|
|
// Move backwards
|
|
if (Hotkeys.IsPressed(_T("Audio Scroll Left"))) {
|
|
UpdatePosition(Position-128,false);
|
|
UpdateImage();
|
|
}
|
|
|
|
// Move forward
|
|
if (Hotkeys.IsPressed(_T("Audio Scroll Right"))) {
|
|
UpdatePosition(Position+128,false);
|
|
UpdateImage();
|
|
}
|
|
|
|
// Play first 500 ms
|
|
if (Hotkeys.IsPressed(_T("Audio Play First 500ms"))) {
|
|
int start=0,end=0;
|
|
GetTimesSelection(start,end);
|
|
int e = start+500;
|
|
if (e > end) e = end;
|
|
Play(start,e);
|
|
}
|
|
|
|
// Play last 500 ms
|
|
if (Hotkeys.IsPressed(_T("Audio Play Last 500ms"))) {
|
|
int start=0,end=0;
|
|
GetTimesSelection(start,end);
|
|
int s = end-500;
|
|
if (s < start) s = start;
|
|
Play(s,end);
|
|
}
|
|
|
|
// Play 500 ms before
|
|
if (Hotkeys.IsPressed(_T("Audio Play 500ms Before"))) {
|
|
int start=0,end=0;
|
|
GetTimesSelection(start,end);
|
|
Play(start-500,start);
|
|
}
|
|
|
|
// Play 500 ms after
|
|
if (Hotkeys.IsPressed(_T("Audio Play 500ms After"))) {
|
|
int start=0,end=0;
|
|
GetTimesSelection(start,end);
|
|
Play(end,end+500);
|
|
}
|
|
|
|
// Play to end of file
|
|
if (Hotkeys.IsPressed(_T("Audio Play To End"))) {
|
|
int start=0,end=0;
|
|
GetTimesSelection(start,end);
|
|
Play(start,-1);
|
|
}
|
|
|
|
// Play original line
|
|
if (Hotkeys.IsPressed(_T("Audio Play Original Line"))) {
|
|
int start=0,end=0;
|
|
GetTimesDialogue(start,end);
|
|
SetSelection(start, end);
|
|
Play(start,end);
|
|
}
|
|
|
|
// Lead in
|
|
if (Hotkeys.IsPressed(_T("Audio Add Lead In"))) {
|
|
AddLead(true,false);
|
|
}
|
|
|
|
// Lead out
|
|
if (Hotkeys.IsPressed(_T("Audio Add Lead Out"))) {
|
|
AddLead(false,true);
|
|
}
|
|
|
|
// Update
|
|
if (diagUpdated) {
|
|
diagUpdated = false;
|
|
NeedCommit = true;
|
|
if (Options.AsBool(_T("Audio Autocommit")) && curStartMS <= curEndMS) CommitChanges();
|
|
else UpdateImage(true);
|
|
}
|
|
}
|
|
|
|
|
|
///////////////
|
|
// Change line
|
|
void AudioDisplay::ChangeLine(int delta, bool block) {
|
|
wxLogDebug(_T("AudioDisplay::ChangeLine(delta=%d)"), delta);
|
|
if (dialogue) {
|
|
wxLogDebug(_T("AudioDisplay::ChangeLine: has dialogue"));
|
|
// Get next line number and make sure it's within bounds
|
|
int next;
|
|
if (block && grid->IsInSelection(line_n)) next = grid->GetLastSelRow()+delta;
|
|
else next = line_n+delta;
|
|
|
|
if (next == -1) next = 0;
|
|
if (next == grid->GetRows()) next = grid->GetRows() - 1;
|
|
wxLogDebug(_T("AudioDisplay::ChangeLine: next=%i"), next);
|
|
|
|
// Set stuff
|
|
NeedCommit = false;
|
|
dialogue = NULL;
|
|
grid->editBox->SetToLine(next);
|
|
grid->SelectRow(next);
|
|
grid->MakeCellVisible(next,0,true);
|
|
if (!dialogue) UpdateImage(true);
|
|
else UpdateImage(false);
|
|
line_n = next;
|
|
}
|
|
wxLogDebug(_T("AudioDisplay::ChangeLine: returning"));
|
|
}
|
|
|
|
|
|
////////
|
|
// Next
|
|
void AudioDisplay::Next(bool play) {
|
|
wxLogDebug(_T("AudioDisplay::Next"));
|
|
// Karaoke
|
|
if (karaoke->enabled) {
|
|
wxLogDebug(_T("AudioDisplay::Next: karaoke enables, going to next syllable"));
|
|
int nextSyl = karaoke->curSyllable+1;
|
|
bool needsUpdate = true;
|
|
|
|
// Last syllable; jump to next
|
|
if (nextSyl >= (signed int)karaoke->syllables.size()) {
|
|
wxLogDebug(_T("AudioDisplay::Next: last syllable on line"));
|
|
// Already last?
|
|
if (line_n == grid->GetRows()-1) return;
|
|
|
|
if (NeedCommit) {
|
|
wxLogDebug(_T("AudioDisplay::Next: uncommitted karaoke changes"));
|
|
int result = wxMessageBox(_("Do you want to commit your changes? If you choose No, they will be discarded."),_("Commit?"),wxYES_NO | wxCANCEL | wxICON_QUESTION);
|
|
//int result = wxNO;
|
|
if (result == wxYES) {
|
|
CommitChanges();
|
|
}
|
|
else if (result == wxCANCEL) {
|
|
wxLogDebug(_T("AudioDisplay::Next: cancelled, returning"));
|
|
karaoke->curSyllable = (int)karaoke->syllables.size()-1;
|
|
return;
|
|
}
|
|
}
|
|
wxLogDebug(_T("AudioDisplay::Next: going to next line"));
|
|
nextSyl = 0;
|
|
karaoke->curSyllable = 0;
|
|
ChangeLine(1);
|
|
needsUpdate = false;
|
|
}
|
|
|
|
// Set syllable
|
|
wxLogDebug(_T("AudioDisplay::Next: set syllable"));
|
|
karaoke->SetSyllable(nextSyl);
|
|
if (needsUpdate) Update();
|
|
int start=0,end=0;
|
|
GetTimesSelection(start,end);
|
|
if (play) Play(start,end);
|
|
}
|
|
|
|
// Plain mode
|
|
else {
|
|
wxLogDebug(_T("AudioDisplay::Next: going to next line"));
|
|
ChangeLine(1);
|
|
}
|
|
|
|
wxLogDebug(_T("AudioDisplay::Next: returning"));
|
|
}
|
|
|
|
|
|
////////////
|
|
// Previous
|
|
void AudioDisplay::Prev(bool play) {
|
|
wxLogDebug(_T("AudioDisplay::Prev"));
|
|
// Karaoke
|
|
if (karaoke->enabled) {
|
|
wxLogDebug(_T("AudioDisplay::Prev: karaoke enabled, going to prev syllable"));
|
|
int nextSyl = karaoke->curSyllable-1;
|
|
bool needsUpdate = true;
|
|
|
|
// First syllable; jump line
|
|
if (nextSyl < 0) {
|
|
wxLogDebug(_T("AudioDisplay::Prev: prev syllable on prev line"));
|
|
// Already first?
|
|
if (line_n == 0) return;
|
|
|
|
if (NeedCommit) {
|
|
wxLogDebug(_T("AudioDisplay::Prev: uncommitted karaoke changes"));
|
|
int result = wxMessageBox(_("Do you want to commit your changes? If you choose No, they will be discarded."),_("Commit?"),wxYES_NO | wxCANCEL);
|
|
if (result == wxYES) {
|
|
CommitChanges();
|
|
}
|
|
else if (result == wxCANCEL) {
|
|
karaoke->curSyllable = 0;
|
|
wxLogDebug(_T("AudioDisplay::Prev: cancelled, returning"));
|
|
return;
|
|
}
|
|
}
|
|
wxLogDebug(_T("AudioDisplay::Prev: going to prev line"));
|
|
karaoke->curSyllable = -1;
|
|
ChangeLine(-1);
|
|
needsUpdate = false;
|
|
}
|
|
|
|
// Set syllable
|
|
wxLogDebug(_T("AudioDisplay::Prev: set syllable"));
|
|
karaoke->SetSyllable(nextSyl);
|
|
if (needsUpdate) Update();
|
|
int start=0,end=0;
|
|
GetTimesSelection(start,end);
|
|
if (play) Play(start,end);
|
|
}
|
|
|
|
// Plain mode
|
|
else {
|
|
wxLogDebug(_T("AudioDisplay::Prev: going to prev line"));
|
|
ChangeLine(-1);
|
|
}
|
|
|
|
wxLogDebug(_T("AudioDisplay::Prev: returning"));
|
|
}
|
|
|
|
|
|
///////////////////////////////
|
|
// Gets syllable at x position
|
|
int AudioDisplay::GetSyllableAtX(int x) {
|
|
if (!karaoke->enabled) return -1;
|
|
int ms = GetMSAtX(x);
|
|
size_t syllables = karaoke->syllables.size();;
|
|
int sylstart,sylend;
|
|
|
|
// Find a matching syllable
|
|
for (size_t i=0;i<syllables;i++) {
|
|
sylstart = karaoke->syllables.at(i).start_time*10 + curStartMS;
|
|
sylend = karaoke->syllables.at(i).duration*10 + sylstart;
|
|
if (ms >= sylstart && ms < sylend) {
|
|
return (int)i;
|
|
}
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
|
|
////////////////
|
|
// Focus events
|
|
void AudioDisplay::OnGetFocus(wxFocusEvent &event) {
|
|
if (!hasFocus) {
|
|
hasFocus = true;
|
|
UpdateImage(true);
|
|
}
|
|
}
|
|
|
|
void AudioDisplay::OnLoseFocus(wxFocusEvent &event) {
|
|
if (hasFocus && loaded) {
|
|
hasFocus = false;
|
|
UpdateImage(true);
|
|
Refresh(false);
|
|
}
|
|
}
|
|
|
|
|
|
//////////////////////////////
|
|
// Update time edit controls
|
|
void AudioDisplay::UpdateTimeEditCtrls() {
|
|
grid->editBox->StartTime->SetTime(curStartMS,true);
|
|
grid->editBox->EndTime->SetTime(curEndMS,true);
|
|
grid->editBox->Duration->SetTime(curEndMS-curStartMS,true);
|
|
}
|