Aegisub/aegisub/src/audio_display.cpp
Niels Martin Hansen 4e417a6dae Change audio autoscroll behaviour:
* 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.
2009-05-14 22:12:05 +00:00

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);
}