1
// Copyright (c) 2005, Rodrigo Braz Monteiro
2
// All rights reserved.
4
// Redistribution and use in source and binary forms, with or without
5
// modification, are permitted provided that the following conditions are met:
7
// * Redistributions of source code must retain the above copyright notice,
8
// this list of conditions and the following disclaimer.
9
// * Redistributions in binary form must reproduce the above copyright notice,
10
// this list of conditions and the following disclaimer in the documentation
11
// and/or other materials provided with the distribution.
12
// * Neither the name of the Aegisub Group nor the names of its contributors
13
// may be used to endorse or promote products derived from this software
14
// without specific prior written permission.
16
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
17
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
18
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
19
// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
20
// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
21
// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
22
// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
23
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
24
// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
25
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
26
// POSSIBILITY OF SUCH DAMAGE.
28
// -----------------------------------------------------------------------------
32
// Website: http://aegisub.cellosoft.com
33
// Contact: mailto:zeratul@cellosoft.com
41
#include <wx/tglbtn.h>
42
#include <wx/filename.h>
45
#include "audio_display.h"
46
#include "audio_provider_stream.h"
48
#include "ass_dialogue.h"
49
#include "subs_grid.h"
51
#include "subs_edit_box.h"
53
#include "audio_karaoke.h"
54
#include "audio_box.h"
56
#include "video_context.h"
58
#include "colorspace.h"
61
#include "timeedit_ctrl.h"
62
#include "standard_paths.h"
64
#include "audio_provider_dummy.h"
69
# define AudioDisplayWindowStyle wxWANTS_CHARS
71
# define AudioDisplayWindowStyle wxSUNKEN_BORDER | wxWANTS_CHARS
76
AudioDisplay::AudioDisplay(wxWindow *parent)
77
: wxWindow (parent, -1, wxDefaultPosition, wxSize(200,Options.AsInt(_T("Audio Display Height"))), AudioDisplayWindowStyle , _T("Audio Display"))
81
spectrumDisplay = NULL;
82
spectrumDisplaySelected = NULL;
83
spectrumRenderer = NULL;
95
dontReadTimes = false;
97
draggingScale = false;
107
samplesPercent = 100;
108
hasFocus = (wxWindow::FindFocus() == this);
109
needImageUpdate = false;
110
needImageUpdateWeak = true;
111
playingToEnd = false;
114
UpdateTimer.SetOwner(this,Audio_Update_Timer);
115
GetClientSize(&w,&h);
116
h -= Options.AsBool(_T("Audio Draw Timeline")) ? 20 : 0;
117
SetSamplesPercent(50,false);
120
//wxCursor cursor(wxCURSOR_BLANK);
123
//wxLog::SetActiveTarget(new wxLogWindow(NULL,_T("Log"),true,false));
129
AudioDisplay::~AudioDisplay() {
130
if (player) player->CloseStream();
134
delete spectrumRenderer;
135
delete spectrumDisplay;
136
delete spectrumDisplaySelected;
142
spectrumRenderer = NULL;
143
spectrumDisplay = NULL;
144
spectrumDisplaySelected = NULL;
152
void AudioDisplay::Reset() {
156
karaoke->enabled = false;
157
karaoke->syllables.clear();
158
box->karaokeMode = false;
159
box->KaraokeButton->SetValue(false);
166
void AudioDisplay::UpdateImage(bool weak) {
170
// Set image as needing to be redrawn
171
needImageUpdate = true;
172
if (weak == false && needImageUpdateWeak == true) {
173
needImageUpdateWeak = false;
178
void AudioDisplay::DoUpdateImage() {
180
if (!loaded || !provider) return;
183
if (!needImageUpdate) return;
184
bool weak = needImageUpdateWeak;
187
int timelineHeight = Options.AsBool(_T("Audio Draw Timeline")) ? 20 : 0;
188
int displayH = h+timelineHeight;
190
if (origImage->GetWidth() != w || origImage->GetHeight() != displayH) {
197
bool draw_boundary_lines = Options.AsBool(_T("Audio Draw Secondary Lines"));
198
bool draw_selection_background = Options.AsBool(_T("Audio Draw Selection Background"));
199
bool drawKeyframes = Options.AsBool(_T("Audio Draw Keyframes"));
201
// Invalid dimensions
202
if (w == 0 || displayH == 0) return;
205
if (!origImage) origImage = new wxBitmap(w,displayH,-1);
208
bool spectrum = false;
209
if (provider && Options.AsBool(_T("Audio Spectrum"))) {
213
// Draw image to be displayed
215
dc.SelectObject(*origImage);
218
dc.SetPen(*wxTRANSPARENT_PEN);
219
dc.SetBrush(wxBrush(Options.AsColour(_T("Audio Background"))));
220
dc.DrawRectangle(0,0,w,h);
222
// Selection position
224
hasKaraoke = karaoke->enabled;
231
int64_t drawSelStart = 0;
232
int64_t drawSelEnd = 0;
234
GetDialoguePos(lineStart,lineEnd,false);
237
GetKaraokePos(selStartCap,selEndCap,true);
238
GetKaraokePos(drawSelStart,drawSelEnd,false);
239
selStart = lineStart;
243
GetDialoguePos(selStartCap,selEndCap,true);
244
selStart = lineStart;
246
drawSelStart = lineStart;
247
drawSelEnd = lineEnd;
252
if (hasSel && drawSelStart < drawSelEnd && draw_selection_background) {
253
if (NeedCommit && !karaoke->enabled) dc.SetBrush(wxBrush(Options.AsColour(_T("Audio Selection Background Modified"))));
254
else dc.SetBrush(wxBrush(Options.AsColour(_T("Audio Selection Background"))));
255
dc.DrawRectangle(drawSelStart,0,drawSelEnd-drawSelStart,h);
260
DrawSpectrum(dc,weak);
265
DrawWaveform(dc,weak);
270
dc.DrawLine(0,h/2,w,h/2);
273
// Draw seconds boundaries
274
if (draw_boundary_lines) {
275
int64_t start = Position*samples;
276
int rate = provider->GetSampleRate();
277
int pixBounds = rate / samples;
278
dc.SetPen(wxPen(Options.AsColour(_T("Audio Seconds Boundaries")),1,wxDOT));
279
if (pixBounds >= 8) {
280
for (int x=0;x<w;x++) {
281
if (((x*samples)+start) % rate < samples) {
282
dc.DrawLine(x,0,x,h);
288
// Draw current frame
289
if (Options.AsBool(_T("Audio Draw Video Position"))) {
290
if (VideoContext::Get()->IsLoaded()) {
291
dc.SetPen(wxPen(Options.AsColour(_T("Audio Play Cursor")),2,wxLONG_DASH));
292
int x = GetXAtMS(VFR_Output.GetTimeAtFrame(VideoContext::Get()->GetFrameN()));
293
dc.DrawLine(x,0,x,h);
298
if (drawKeyframes && VideoContext::Get()->KeyFramesLoaded()) {
302
// Draw previous line
303
DrawInactiveLines(dc);
308
// Draw start boundary
309
int selWidth = Options.AsInt(_T("Audio Line boundaries Thickness"));
310
dc.SetPen(wxPen(Options.AsColour(_T("Audio Line boundary start"))));
311
dc.SetBrush(wxBrush(Options.AsColour(_T("Audio Line boundary start"))));
312
dc.DrawRectangle(lineStart-selWidth/2+1,0,selWidth,h);
313
wxPoint points1[3] = { wxPoint(lineStart,0), wxPoint(lineStart+10,0), wxPoint(lineStart,10) };
314
wxPoint points2[3] = { wxPoint(lineStart,h-1), wxPoint(lineStart+10,h-1), wxPoint(lineStart,h-11) };
315
dc.DrawPolygon(3,points1);
316
dc.DrawPolygon(3,points2);
319
dc.SetPen(wxPen(Options.AsColour(_T("Audio Line boundary end"))));
320
dc.SetBrush(wxBrush(Options.AsColour(_T("Audio Line boundary end"))));
321
dc.DrawRectangle(lineEnd-selWidth/2+1,0,selWidth,h);
322
wxPoint points3[3] = { wxPoint(lineEnd,0), wxPoint(lineEnd-10,0), wxPoint(lineEnd,10) };
323
wxPoint points4[3] = { wxPoint(lineEnd,h-1), wxPoint(lineEnd-10,h-1), wxPoint(lineEnd,h-11) };
324
dc.DrawPolygon(3,points3);
325
dc.DrawPolygon(3,points4);
332
wxPen curPen(Options.AsColour(_T("Audio Syllable boundaries")),1,wxDOT);
334
wxFont curFont(9,wxFONTFAMILY_DEFAULT,wxFONTSTYLE_NORMAL,wxFONTWEIGHT_BOLD,false,_T("Verdana"),wxFONTENCODING_SYSTEM);
336
if (!spectrum) dc.SetTextForeground(Options.AsColour(_T("Audio Syllable text")));
337
else dc.SetTextForeground(wxColour(255,255,255));
338
size_t karn = karaoke->syllables.size();
342
AudioKaraokeSyllable *curSyl;
346
for (size_t i=0;i<karn;i++) {
347
curSyl = &karaoke->syllables.at(i);
348
len = curSyl->duration*10;
349
curpos = curSyl->start_time*10;
351
pos1 = GetXAtMS(curStartMS+curpos);
352
pos2 = GetXAtMS(curStartMS+len+curpos);
353
dc.DrawLine(pos2,0,pos2,h);
354
temptext = curSyl->text;
356
temptext.Trim(false);
357
GetTextExtent(temptext,&tw,&th,NULL,NULL,&curFont);
358
dc.DrawText(temptext,(pos1+pos2-tw)/2,4);
370
dc.SetFont(wxFont(9,wxDEFAULT,wxFONTSTYLE_NORMAL,wxFONTWEIGHT_BOLD,false,_T("Verdana"))); // FIXME: hardcoded font name
371
dc.SetTextForeground(wxColour(255,0,0));
372
if (selStart <= selEnd) {
373
dc.DrawText(_T("Modified"),4,4);
376
dc.DrawText(_T("Negative time"),4,4);
381
if (timelineHeight) {
385
// Draw selection border
387
dc.SetPen(*wxGREEN_PEN);
388
dc.SetBrush(*wxTRANSPARENT_BRUSH);
389
dc.DrawRectangle(0,0,w,h);
393
needImageUpdate = false;
394
needImageUpdateWeak = true;
398
///////////////////////
399
// Draw Inactive Lines
400
void AudioDisplay::DrawInactiveLines(wxDC &dc) {
401
// Check if there is anything to do
402
int shadeType = Options.AsInt(_T("Audio Inactive Lines Display Mode"));
403
if (shadeType == 0) return;
406
bool spectrum = false;
407
if (provider && Options.AsBool(_T("Audio Spectrum"))) {
412
dc.SetBrush(wxBrush(Options.AsColour(_T("Audio Line boundary inactive line"))));
413
int selWidth = Options.AsInt(_T("Audio Line boundaries Thickness"));
416
int shadeFrom,shadeTo;
419
if (shadeType == 1) {
420
shadeFrom = this->line_n-1;
421
shadeTo = shadeFrom+1;
427
shadeTo = grid->GetRows();
430
for (int j=shadeFrom;j<shadeTo;j++) {
431
if (j == line_n) continue;
433
shade = grid->GetDialogue(j);
437
shadeX1 = GetXAtMS(shade->Start.GetMS());
438
shadeX2 = GetXAtMS(shade->End.GetMS());
439
if (shadeX2 < 0 || shadeX1 > w) continue;
441
// Draw over waveform
444
int selX1 = MAX(0,GetXAtMS(curStartMS));
445
int selX2 = MIN(w,GetXAtMS(curEndMS));
447
// Get ranges (x1->x2, x3->x4).
448
int x1 = MAX(0,shadeX1);
449
int x2 = MIN(w,shadeX2);
450
int x3 = MAX(x1,selX2);
451
int x4 = MAX(x2,selX2);
458
dc.SetPen(wxPen(Options.AsColour(_T("Audio Waveform Inactive"))));
459
for (int i=x1;i<x2;i++) dc.DrawLine(i,peak[i],i,min[i]-1);
460
for (int i=x3;i<x4;i++) dc.DrawLine(i,peak[i],i,min[i]-1);
464
dc.SetPen(wxPen(Options.AsColour(_T("Audio Line boundary inactive line"))));
465
dc.DrawRectangle(shadeX1-selWidth/2+1,0,selWidth,h);
466
dc.DrawRectangle(shadeX2-selWidth/2+1,0,selWidth,h);
474
void AudioDisplay::DrawKeyframes(wxDC &dc) {
475
const wxArrayInt & KeyFrames = VideoContext::Get()->GetKeyFrames();
476
int nKeys = (int)KeyFrames.Count();
477
dc.SetPen(wxPen(wxColour(255,0,255),1));
479
// Get min and max frames to care about
480
int minFrame = VFR_Output.GetFrameAtTime(GetMSAtX(0),true);
481
int maxFrame = VFR_Output.GetFrameAtTime(GetMSAtX(w),true);
484
for (int i=0;i<nKeys;i++) {
485
int cur = KeyFrames[i];
486
if (cur >= minFrame && cur <= maxFrame) {
487
int x = GetXAtMS(VFR_Output.GetTimeAtFrame(cur,true));
488
dc.DrawLine(x,0,x,h);
490
else if (cur > maxFrame) break;
497
void AudioDisplay::DrawTimescale(wxDC &dc) {
499
int timelineHeight = Options.AsBool(_T("Audio Draw Timeline")) ? 20 : 0;
502
dc.SetBrush(wxSystemSettings::GetColour(wxSYS_COLOUR_BTNFACE));
503
dc.SetPen(*wxTRANSPARENT_PEN);
504
dc.DrawRectangle(0,h,w,timelineHeight);
505
dc.SetPen(wxSystemSettings::GetColour(wxSYS_COLOUR_3DLIGHT));
506
dc.DrawLine(0,h,w,h);
507
dc.SetPen(wxSystemSettings::GetColour(wxSYS_COLOUR_3DHIGHLIGHT));
508
dc.DrawLine(0,h+1,w,h+1);
509
dc.SetPen(wxSystemSettings::GetColour(wxSYS_COLOUR_BTNTEXT));
510
dc.SetTextForeground(wxSystemSettings::GetColour(wxSYS_COLOUR_BTNTEXT));
512
scaleFont.SetFaceName(_T("Tahoma")); // FIXME: hardcoded font name
513
if (!scaleFont.IsOk())
514
scaleFont.SetFamily(wxFONTFAMILY_SWISS );
515
scaleFont.SetPointSize(8);
516
dc.SetFont(scaleFont);
519
int64_t start = Position*samples;
520
int rate = provider->GetSampleRate();
521
for (int i=1;i<32;i*=2) {
522
int pixBounds = rate / (samples * 4 / i);
523
if (pixBounds >= 8) {
524
for (int x=0;x<w;x++) {
525
int64_t pos = (x*samples)+start;
527
if (pos % rate < samples) {
528
dc.DrawLine(x,h+2,x,h+8);
544
if (hr) text = wxString::Format(_T("%i:%02i:%02i"),hr,m,s);
545
else if (m) text = wxString::Format(_T("%i:%02i"),m,s);
546
else text = wxString::Format(_T("%i"),s);
547
dc.GetTextExtent(text,&textW,&textH,NULL,NULL,&scaleFont);
548
dc.DrawText(text,MAX(0,x-textW/2)+1,h+8);
552
else if (pos % (rate / 4 * i) < samples) {
553
dc.DrawLine(x,h+2,x,h+5);
564
void AudioDisplay::DrawWaveform(wxDC &dc,bool weak) {
566
if (!weak || peak == NULL || min == NULL) {
567
if (peak) delete[] peak;
568
if (min) delete[] min;
575
provider->GetWaveForm(min,peak,Position*samples,w,h,samples,scale);
578
// Draw pre-selection
579
if (!hasSel) selStartCap = w;
580
dc.SetPen(wxPen(Options.AsColour(_T("Audio Waveform"))));
581
for (int64_t i=0;i<selStartCap;i++) {
582
dc.DrawLine(i,peak[i],i,min[i]-1);
587
if (Options.AsBool(_T("Audio Draw Selection Background"))) {
588
if (NeedCommit && !karaoke->enabled) dc.SetPen(wxPen(Options.AsColour(_T("Audio Waveform Modified"))));
589
else dc.SetPen(wxPen(Options.AsColour(_T("Audio Waveform Selected"))));
591
for (int64_t i=selStartCap;i<selEndCap;i++) {
592
dc.DrawLine(i,peak[i],i,min[i]-1);
595
// Draw post-selection
596
dc.SetPen(wxPen(Options.AsColour(_T("Audio Waveform"))));
597
for (int64_t i=selEndCap;i<w;i++) {
598
dc.DrawLine(i,peak[i],i,min[i]-1);
604
//////////////////////////
605
// Draw spectrum analyzer
606
void AudioDisplay::DrawSpectrum(wxDC &finaldc,bool weak) {
607
if (!weak || !spectrumDisplay || spectrumDisplay->GetWidth() != w || spectrumDisplay->GetHeight() != h) {
608
if (spectrumDisplay) {
609
delete spectrumDisplay;
610
delete spectrumDisplaySelected;
612
spectrumDisplaySelected = 0;
618
if (!spectrumRenderer)
619
spectrumRenderer = new AudioSpectrum(provider);
621
spectrumRenderer->SetScaling(scale);
623
unsigned char *img = (unsigned char *)malloc(h*w*3); // wxImage requires using malloc
625
// Use a slightly slower, but simple way
626
// Always draw the spectrum for the entire width
627
// Hack: without those divs by 2 the display is horizontally compressed
628
spectrumRenderer->RenderRange(Position*samples, (Position+w)*samples, false, img, 0, w, w, h);
630
// The spectrum bitmap will have been deleted above already, so just make a new one
631
wxImage imgobj(w, h, img, false);
632
spectrumDisplay = new wxBitmap(imgobj);
635
if (hasSel && selStartCap < selEndCap && !spectrumDisplaySelected) {
636
// There is a visible selection and we don't have a rendered one
637
// This should be done regardless whether we're "weak" or not
638
// Assume a few things were already set up when things were first rendered though
639
unsigned char *img = (unsigned char *)malloc(h*w*3);
640
spectrumRenderer->RenderRange(Position*samples, (Position+w)*samples, true, img, 0, w, w, h);
641
wxImage imgobj(w, h, img, false);
642
spectrumDisplaySelected = new wxBitmap(imgobj);
647
dc.SelectObject(*spectrumDisplay);
648
finaldc.Blit(0,0,w,h,&dc,0,0);
650
if (hasSel && spectrumDisplaySelected && selStartCap < selEndCap) {
651
dc.SelectObject(*spectrumDisplaySelected);
652
finaldc.Blit(selStartCap, 0, selEndCap-selStartCap, h, &dc, selStartCap, 0);
656
//////////////////////////
657
// Get selection position
658
void AudioDisplay::GetDialoguePos(int64_t &selStart,int64_t &selEnd, bool cap) {
659
selStart = GetXAtMS(curStartMS);
660
selEnd = GetXAtMS(curEndMS);
663
if (selStart < 0) selStart = 0;
664
if (selEnd < 0) selEnd = 0;
665
if (selStart >= w) selStart = w-1;
666
if (selEnd >= w) selEnd = w-1;
671
////////////////////////
672
// Get karaoke position
673
void AudioDisplay::GetKaraokePos(int64_t &karStart,int64_t &karEnd, bool cap) {
676
int nsyls = (int)karaoke->syllables.size();
677
if (karaoke->curSyllable == -1) {
678
karaoke->SetSyllable(nsyls-1);
680
if (karaoke->curSyllable >= nsyls) karaoke->curSyllable = nsyls-1;
683
int pos = karaoke->syllables.at(karaoke->curSyllable).start_time;
684
int len = karaoke->syllables.at(karaoke->curSyllable).duration;
685
karStart = GetXAtMS(curStartMS+pos*10);
686
karEnd = GetXAtMS(curStartMS+pos*10+len*10);
690
if (karStart < 0) karStart = 0;
691
if (karEnd < 0) karEnd = 0;
692
if (karStart >= w) karStart = w-1;
693
if (karEnd >= w) karEnd = w-1;
703
void AudioDisplay::Update() {
704
if (blockUpdate) return;
706
if (Options.AsBool(_T("Audio Autoscroll")))
707
MakeDialogueVisible();
714
//////////////////////
715
// Recreate the image
716
void AudioDisplay::RecreateImage() {
717
GetClientSize(&w,&h);
718
h -= Options.AsBool(_T("Audio Draw Timeline")) ? 20 : 0;
725
/////////////////////////
726
// Make dialogue visible
727
void AudioDisplay::MakeDialogueVisible(bool force) {
729
int startShow=0, endShow=0;
730
if (karaoke->enabled) {
731
// In karaoke mode the syllable and as much as possible towards the end of the line should be shown
733
GetTimesSelection(startShow, dummy);
734
GetTimesDialogue(dummy, endShow);
736
GetTimesSelection(startShow,endShow);
738
int startPos = GetSampleAtMS(startShow);
739
int endPos = GetSampleAtMS(endShow);
740
int startX = GetXAtMS(startShow);
741
int endX = GetXAtMS(endShow);
743
if (force || startX < 50 || endX > w-50) {
744
if (startX < 50 || endX - startX >= w) {
745
// Make sure the left edge of the selection is at least 50 pixels from the edge of the display
746
UpdatePosition(startPos - 50*samples, true);
748
// Otherwise center the selection in display
749
UpdatePosition((startPos+endPos-w*samples)/2,true);
760
void AudioDisplay::SetPosition(int pos) {
762
PositionSample = pos * samples;
769
void AudioDisplay::UpdatePosition (int pos,bool IsSample) {
771
if (!provider) return;
772
if (IsSample) pos /= samples;
773
int len = provider->GetNumSamples() / samples;
774
if (pos < 0) pos = 0;
775
if (pos >= len) pos = len-1;
779
PositionSample = pos*samples;
784
/////////////////////////////
785
// Set samples in percentage
786
// Note: aka Horizontal Zoom
787
void AudioDisplay::SetSamplesPercent(int percent,bool update,float pivot) {
789
if (percent < 1) percent = 1;
790
if (percent > 100) percent = 100;
791
if (samplesPercent == percent) return;
792
samplesPercent = percent;
797
int oldSamples = samples;
799
PositionSample += int64_t((oldSamples-samples)*w*pivot);
800
if (PositionSample < 0) PositionSample = 0;
813
void AudioDisplay::UpdateSamples() {
815
if (!provider) return;
817
int64_t totalSamples = provider->GetNumSamples();
818
int total = totalSamples / w;
819
int max = 5760000 / w; // 2 minutes at 48 kHz maximum
820
if (total > max) total = max;
822
if (total < min) total = min;
823
int range = total-min;
824
samples = int(range*pow(samplesPercent/100.0,3)+min);
827
int length = w * samples;
828
if (PositionSample + length > totalSamples) {
829
PositionSample = totalSamples - length;
830
if (PositionSample < 0) PositionSample = 0;
831
if (samples) Position = PositionSample / samples;
839
void AudioDisplay::SetScale(float _scale) {
840
if (scale == _scale) return;
848
void AudioDisplay::SetFile(wxString file) {
850
if (file.IsEmpty()) try {
852
if (player) player->CloseStream();
854
catch (const wxChar *e) {
859
delete spectrumRenderer;
862
spectrumRenderer = NULL;
866
catch (const wxChar *e) {
872
StandardPaths::SetPathValue(_T("?audio"),_T(""));
877
catch (const wxChar *e) {
881
wxLogError(_T("Unknown error unloading audio"));
889
bool is_dummy = false;
891
if (file == _T("?dummy")) {
893
provider = new DummyAudioProvider(150*60*1000, false); // 150 minutes non-noise
894
} else if (file == _T("?noise")) {
896
provider = new DummyAudioProvider(150*60*1000, true); // 150 minutes noise
898
provider = AudioProviderFactoryManager::GetAudioProvider(file);
901
provider = AudioProviderFactoryManager::GetAudioProvider(file);
905
player = AudioPlayerFactoryManager::GetAudioPlayer();
906
player->SetDisplayTimer(&UpdateTimer);
907
player->SetProvider(provider);
908
player->OpenStream();
913
Options.AddToRecentList(file,_T("Recent aud"));
915
StandardPaths::SetPathValue(_T("?audio"),fn.GetPath());
921
catch (AudioProvider::CancelAudioLoadException &) {
922
if (player) { delete player; player = 0; }
923
if (provider) { delete provider; provider = 0; }
925
catch (const wxChar *e) {
926
if (player) { delete player; player = 0; }
927
if (provider) { delete provider; provider = 0; }
930
catch (wxString &err) {
931
if (player) { delete player; player = 0; }
932
if (provider) { delete provider; provider = 0; }
933
wxMessageBox(err,_T("Error loading audio"),wxICON_ERROR | wxOK);
936
if (player) { delete player; player = 0; }
937
if (provider) { delete provider; provider = 0; }
938
wxLogError(_T("Unknown error loading audio"));
944
assert(loaded == (provider != NULL));
946
// Set default selection
947
int n = grid->editBox->linen;
948
SetDialogue(grid,grid->GetDialogue(n),n);
954
void AudioDisplay::SetFromVideo() {
955
if (VideoContext::Get()->IsLoaded()) {
956
wxString extension = VideoContext::Get()->videoName.Right(4);
957
extension.LowerCase();
959
if (extension != _T(".d2v")) SetFile(VideoContext::Get()->videoName);
966
void AudioDisplay::Reload() {
967
if (provider) SetFile(provider->GetFilename());
973
void AudioDisplay::UpdateScrollbar() {
974
if (!provider) return;
976
int len = provider->GetNumSamples() / samples / 12;
977
Position = PositionSample / samples;
978
ScrollBar->SetScrollbar(Position/12,page,len,int(page*0.7),true);
982
//////////////////////////////////////////////
983
// Gets the sample number at the x coordinate
984
int64_t AudioDisplay::GetSampleAtX(int x) {
985
return (x+Position)*samples;
989
/////////////////////////////////////////////////
990
// Gets the x coordinate corresponding to sample
991
int AudioDisplay::GetXAtSample(int64_t n) {
992
return samples ? (n/samples)-Position : 0;
998
int AudioDisplay::GetMSAtX(int64_t x) {
999
return (PositionSample+(x*samples)) * 1000 / provider->GetSampleRate();
1005
int AudioDisplay::GetXAtMS(int64_t ms) {
1006
return ((ms * provider->GetSampleRate() / 1000)-PositionSample)/samples;
1010
////////////////////
1012
int AudioDisplay::GetMSAtSample(int64_t x) {
1013
return x * 1000 / provider->GetSampleRate();
1017
////////////////////
1019
int64_t AudioDisplay::GetSampleAtMS(int64_t ms) {
1020
return ms * provider->GetSampleRate() / 1000;
1026
void AudioDisplay::Play(int start,int end) {
1035
playingToEnd = end < 0;
1036
int64_t num_samples = provider->GetNumSamples();
1037
start = GetSampleAtMS(start);
1038
if (end != -1) end = GetSampleAtMS(end);
1039
else end = num_samples-1;
1042
if (start < 0) start = 0;
1043
if (start >= num_samples) start = num_samples-1;
1044
if (end >= num_samples) end = num_samples-1;
1045
if (end < start) end = start;
1047
// Redraw the image to avoid any junk left over from mouse movements etc
1052
player->Play(start,end-start);
1058
void AudioDisplay::Stop() {
1059
if (VideoContext::Get()->IsPlaying()) VideoContext::Get()->Stop();
1060
if (player) player->Stop();
1064
///////////////////////////
1065
// Get samples of dialogue
1066
void AudioDisplay::GetTimesDialogue(int &start,int &end) {
1073
start = dialogue->Start.GetMS();
1074
end = dialogue->End.GetMS();
1078
////////////////////////////
1079
// Get samples of selection
1080
void AudioDisplay::GetTimesSelection(int &start,int &end) {
1083
if (!dialogue) return;
1086
if (karaoke->enabled) {
1087
int pos = karaoke->syllables.at(karaoke->curSyllable).start_time;
1088
int len = karaoke->syllables.at(karaoke->curSyllable).duration;
1089
start = curStartMS+pos*10;
1090
end = curStartMS+pos*10+len*10;
1101
/////////////////////////////
1102
// Set the current selection
1103
void AudioDisplay::SetSelection(int start, int end) {
1112
void AudioDisplay::SetDialogue(SubtitlesGrid *_grid,AssDialogue *diag,int n) {
1113
// Actual parameters
1121
diagUpdated = false;
1125
if (dialogue && !dontReadTimes && Options.AsBool(_T("Audio grab times on select"))) {
1126
int s = dialogue->Start.GetMS();
1127
int e = dialogue->End.GetMS();
1129
// Never do it for 0:00:00.00->0:00:00.00 lines
1130
if (s != 0 || e != 0) {
1137
// Read karaoke data
1138
if (dialogue && karaoke->enabled) {
1139
NeedCommit = karaoke->LoadFromDialogue(dialogue);
1141
// Reset karaoke pos
1142
if (karaoke->curSyllable == -1) karaoke->SetSyllable((int)karaoke->syllables.size()-1);
1143
else karaoke->SetSyllable(0);
1153
void AudioDisplay::CommitChanges (bool nextLine) {
1155
if (!loaded) return;
1158
bool textNeedsCommit = grid->GetDialogue(line_n)->Text != grid->editBox->TextEdit->GetText();
1159
bool timeNeedsCommit = grid->GetDialogue(line_n)->Start.GetMS() != curStartMS || grid->GetDialogue(line_n)->End.GetMS() != curEndMS;
1160
if (timeNeedsCommit || textNeedsCommit) NeedCommit = true;
1161
bool wasKaraSplitting = false;
1162
bool validCommit = true;
1163
if (!karaoke->enabled && !karaoke->splitting) {
1164
if (!NeedCommit || curEndMS < curStartMS) validCommit = false;
1168
int karaSelStart = 0, karaSelEnd = -1;
1169
if (karaoke->enabled) {
1170
wasKaraSplitting = karaoke->splitting;
1172
// Get karaoke selection
1173
karaSelStart = karaoke->syllables.size();
1174
for (size_t k = 0; k < karaoke->syllables.size(); ++k) {
1175
if (karaoke->syllables[k].selected) {
1176
if ((signed)k < karaSelStart) karaSelStart = k;
1177
if ((signed)k > karaSelEnd) karaSelEnd = k;
1182
// Get selected rows
1183
wxArrayInt sel = grid->GetSelection();
1188
diagUpdated = false;
1193
AssDialogue *curDiag;
1194
for (size_t i=0;i<sel.GetCount();i++) {
1195
if (grid->IsInSelection(line_n)) curDiag = grid->GetDialogue(sel[i]);
1196
else curDiag = grid->GetDialogue(line_n);
1197
if (timeNeedsCommit) {
1198
curDiag->Start.SetMS(curStartMS);
1199
curDiag->End.SetMS(curEndMS);
1201
if (!karaoke->enabled && textNeedsCommit) {
1202
// If user was editing karaoke stuff, that should take precedence of manual changes in the editbox,
1203
// so only update from editbox when not in kara mode
1204
curDiag->Text = grid->editBox->TextEdit->GetText();
1206
curDiag->UpdateData();
1207
if (!grid->IsInSelection(line_n)) break;
1211
grid->editBox->StartTime->Update();
1212
grid->editBox->EndTime->Update();
1213
grid->editBox->Duration->Update();
1216
grid->editBox->Update(!karaoke->enabled);
1217
grid->ass->FlagAsModified(_T(""));
1218
grid->CommitChanges();
1219
karaoke->SetSelection(karaSelStart, karaSelEnd);
1220
blockUpdate = false;
1223
// Next line (ugh what a condition, can this be simplified?)
1224
if (nextLine && !karaoke->enabled && Options.AsBool(_T("Audio Next Line on Commit")) && !wasKaraSplitting) {
1225
// Insert a line if it doesn't exist
1226
int nrows = grid->GetRows();
1227
if (nrows == line_n + 1) {
1228
AssDialogue *def = new AssDialogue;
1229
def->Start = grid->GetDialogue(line_n)->End;
1230
def->End = grid->GetDialogue(line_n)->End;
1231
def->End.SetMS(def->End.GetMS()+Options.AsInt(_T("Timing Default Duration")));
1232
def->Style = grid->GetDialogue(line_n)->Style;
1233
grid->InsertLine(def,line_n,true);
1234
curStartMS = curEndMS;
1235
curEndMS = curStartMS + Options.AsInt(_T("Timing Default Duration"));
1237
else if (grid->GetDialogue(line_n+1)->Start.GetMS() == 0 && grid->GetDialogue(line_n+1)->End.GetMS() == 0) {
1238
curStartMS = curEndMS;
1239
curEndMS = curStartMS + Options.AsInt(_T("Timing Default Duration"));
1242
curStartMS = grid->GetDialogue(line_n+1)->Start.GetMS();
1243
curEndMS = grid->GetDialogue(line_n+1)->End.GetMS();
1247
dontReadTimes = true;
1248
ChangeLine(1,sel.GetCount() > 1 ? true : false);
1249
dontReadTimes = false;
1258
void AudioDisplay::AddLead(bool in,bool out) {
1261
curStartMS -= Options.AsInt(_T("Audio Lead in"));
1262
if (curStartMS < 0) curStartMS = 0;
1267
curEndMS += Options.AsInt(_T("Audio Lead out"));
1271
UpdateTimeEditCtrls();
1273
if (Options.AsBool(_T("Audio Autocommit"))) CommitChanges();
1280
BEGIN_EVENT_TABLE(AudioDisplay, wxWindow)
1281
EVT_MOUSE_EVENTS(AudioDisplay::OnMouseEvent)
1282
EVT_PAINT(AudioDisplay::OnPaint)
1283
EVT_SIZE(AudioDisplay::OnSize)
1284
EVT_TIMER(Audio_Update_Timer,AudioDisplay::OnUpdateTimer)
1285
EVT_KEY_DOWN(AudioDisplay::OnKeyDown)
1286
EVT_SET_FOCUS(AudioDisplay::OnGetFocus)
1287
EVT_KILL_FOCUS(AudioDisplay::OnLoseFocus)
1293
void AudioDisplay::OnPaint(wxPaintEvent& event) {
1294
if (w == 0 || h == 0) return;
1298
if (origImage) dc.DrawBitmap(*origImage,0,0);
1301
// Hack/workaround for wxMac limitation
1302
// Painting to a wxClientDC doesn't work on wxMac so use this hack
1303
// instead to paint the playback cursor in OnPaint, and shoot Refresh()
1304
// in the OnUpdateTimer handler.
1305
// The playback cursor doesn't disappear after stopping in every case,
1307
if (player && !player->IsPlaying())
1308
playbackCursorPos = -1;
1309
if (playbackCursorPos >= 0) {
1310
dc.SetPen(wxPen(Options.AsColour(_T("Audio Play cursor"))));
1311
dc.DrawLine(playbackCursorPos,0,playbackCursorPos,h);
1319
void AudioDisplay::OnMouseEvent(wxMouseEvent& event) {
1321
int64_t x = event.GetX();
1322
int64_t y = event.GetY();
1323
bool karMode = karaoke->enabled;
1324
bool shiftDown = event.m_shiftDown;
1325
int timelineHeight = Options.AsBool(_T("Audio Draw Timeline")) ? 20 : 0;
1328
if (event.Leaving()) {
1333
if (!player || !provider) {
1339
bool inside = false;
1340
bool onScale = false;
1341
if (x >= 0 && y >= 0 && x < w) {
1346
if (wxWindow::FindFocus() != this && Options.AsBool(_T("Audio Autofocus"))) SetFocus();
1348
else if (y < h+timelineHeight) onScale = true;
1352
bool leftIsDown = event.ButtonIsDown(wxMOUSE_BTN_LEFT);
1353
bool rightIsDown = event.ButtonIsDown(wxMOUSE_BTN_RIGHT);
1354
bool buttonIsDown = leftIsDown || rightIsDown;
1355
bool leftClick = event.ButtonDown(wxMOUSE_BTN_LEFT);
1356
bool rightClick = event.ButtonDown(wxMOUSE_BTN_RIGHT);
1357
bool middleClick = event.Button(wxMOUSE_BTN_MIDDLE);
1358
bool buttonClick = leftClick || rightClick;
1359
bool defCursor = true;
1362
if (buttonClick && !holding) {
1366
if (!buttonIsDown && holding) {
1368
if (HasCapture()) ReleaseMouse();
1372
if (event.GetWheelRotation() != 0) {
1374
bool zoom = shiftDown;
1375
if (Options.AsBool(_T("Audio Wheel Default To Zoom"))) zoom = !zoom;
1380
// Reverse scroll directions on Apple... ugly hack
1381
// Otherwise left=right and right=left on systems that support four-way scrolling.
1382
int step = -event.GetWheelRotation() / event.GetWheelDelta();
1384
int step = event.GetWheelRotation() / event.GetWheelDelta();
1386
int value = box->HorizontalZoom->GetValue()+step;
1387
box->HorizontalZoom->SetValue(value);
1388
SetSamplesPercent(value,true,float(x)/float(w));
1393
int step = -event.GetWheelRotation() * w / 360;
1394
UpdatePosition(Position+step,false);
1400
if (player && !player->IsPlaying() && origImage) {
1402
wxClientDC dc(this);
1403
dc.DrawBitmap(*origImage,0,0);
1407
dc.SetLogicalFunction(wxINVERT);
1408
dc.DrawLine(x,0,x,h);
1411
if (Options.AsBool(_T("Audio Draw Cursor Time"))) {
1414
time.SetMS(GetMSAtX(x));
1415
wxString text = time.GetASSFormated();
1417
// Calculate metrics
1418
// FIXME: Hardcoded font name
1419
wxFont font(10,wxFONTFAMILY_DEFAULT,wxFONTSTYLE_NORMAL,wxFONTWEIGHT_BOLD,false,_T("Verdana"));
1422
GetTextExtent(text,&tw,&th,NULL,NULL,&font);
1426
if (x > w/2) left = false;
1432
int max = w - tw - 4;
1433
if (dx > max) dx = max;
1435
if (karMode) dy += th;
1438
dc.SetTextForeground(wxColour(64,64,64));
1439
dc.DrawText(text,dx+1,dy-1);
1440
dc.DrawText(text,dx+1,dy+1);
1441
dc.DrawText(text,dx-1,dy-1);
1442
dc.DrawText(text,dx-1,dy+1);
1443
dc.SetTextForeground(wxColour(255,255,255));
1444
dc.DrawText(text,dx,dy);
1450
if ((hold == 0 && onScale) || draggingScale) {
1451
if (event.ButtonDown(wxMOUSE_BTN_LEFT)) {
1453
draggingScale = true;
1456
int delta = lastDragX - x;
1458
UpdatePosition(Position + delta);
1461
SetCursor(wxNullCursor);
1464
else draggingScale = false;
1468
if (!inside && hold == 0) return;
1478
if (karaoke->enabled) {
1479
int syl = GetSyllableAtX(x);
1481
int start = karaoke->syllables.at(syl).start_time * 10 + dialogue->Start.GetMS();
1482
int count = karaoke->syllables.at(syl).duration * 10;
1483
Play(start, start+count);
1491
if (VideoContext::Get()->IsLoaded()) {
1492
VideoContext::Get()->JumpToTime(GetMSAtX(x),true);
1498
bool updated = false;
1502
bool gotGrab = false;
1503
bool karTime = karMode && !
1507
event.ControlDown();
1513
if (abs64 (x - selStart) < 6 && Options.AsBool(_T("Disable Dragging Times"))==false) {
1514
wxCursor cursor(wxCURSOR_SIZEWE);
1524
else if (abs64 (x - selEnd) < 6 && Options.AsBool(_T("Disable Dragging Times"))==false) {
1525
wxCursor cursor(wxCURSOR_SIZEWE);
1534
// Dragging nothing, time from scratch
1537
if (leftClick) hold = 3;
1547
// Look for a syllable
1548
int64_t pos,len,curpos;
1549
AudioKaraokeSyllable *curSyl;
1550
size_t karn = karaoke->syllables.size();
1551
for (size_t i=0;i<karn;i++) {
1552
curSyl = &karaoke->syllables.at(i);
1553
len = curSyl->duration*10;
1554
curpos = curSyl->start_time*10;
1556
pos = GetXAtMS(curStartMS+len+curpos);
1558
// Grabbing syllable boundary
1559
if (abs64 (x - pos) < 7) {
1560
wxCursor cursor(wxCURSOR_SIZEWE);
1563
if (event.LeftIsDown()) {
1573
// No syllable found, select if possible
1574
if (hold == 0 && leftClick) {
1575
int syl = GetSyllableAtX(x);
1577
karaoke->SetSyllable(syl);
1588
// Drag from nothing or straight timing
1589
if (hold == 3 && buttonIsDown) {
1591
if (leftIsDown) curStartMS = GetMSAtX(x);
1592
else curEndMS = GetMSAtX(x);
1596
if (leftIsDown && abs((long)(x-lastX)) > Options.AsInt(_T("Audio Start Drag Sensitivity"))) {
1599
curStartMS = GetBoundarySnap(GetMSAtX(lastX),10,event.ShiftDown(),true);
1600
curEndMS = GetMSAtX(x);
1607
if (hold == 1 && buttonIsDown) {
1609
if (x != selStart) {
1610
int snapped = GetBoundarySnap(GetMSAtX(x),10,event.ShiftDown(),true);
1611
selStart = GetXAtMS(snapped);
1612
if (selStart > selEnd) {
1613
int temp = selStart;
1618
snapped = GetMSAtX(selStart);
1620
curStartMS = snapped;
1627
if (hold == 2 && buttonIsDown) {
1630
int snapped = GetBoundarySnap(GetMSAtX(x),10,event.ShiftDown(),false);
1631
selEnd = GetXAtMS(snapped);
1632
//selEnd = GetBoundarySnap(x,event.ShiftDown()?0:10,false);
1633
if (selStart > selEnd) {
1634
int temp = selStart;
1638
curStartMS = snapped;
1639
snapped = GetMSAtX(selEnd);
1648
if (hold == 4 && leftIsDown) {
1650
int curpos,len,pos,nkar;
1651
AudioKaraokeSyllable *curSyl=NULL,*nextSyl=NULL;
1652
curSyl = &karaoke->syllables.at(holdSyl);
1653
nkar = (int)karaoke->syllables.size();
1654
if (holdSyl < nkar-1) {
1655
nextSyl = &karaoke->syllables.at(holdSyl+1);
1657
curpos = curSyl->start_time;
1658
len = curSyl->duration;
1659
pos = GetXAtMS(curStartMS+(len+curpos)*10);
1661
// Calculate delta in centiseconds
1662
int delta = ((int64_t)(x-pos)*samples*100)/provider->GetSampleRate();
1666
if (shiftDown) deltaMode = 1;
1667
// else if (ctrlDown) deltaMode = 2;
1668
bool result = karaoke->SyllableDelta(holdSyl,delta,deltaMode);
1679
// Prevent negative times
1680
curStartMS = MAX(0, curStartMS);
1681
curEndMS = MAX(0, curEndMS);
1682
selStart = MAX(0, selStart);
1683
selEnd = MAX(0, selEnd);
1686
diagUpdated = false;
1687
// Time edit controls changed, or it was a karaoke syllable drag
1688
NeedCommit = UpdateTimeEditCtrls() || (hold == 4);
1690
if (NeedCommit && Options.AsBool(_T("Audio Autocommit")))
1696
SetCursor(wxNullCursor);
1703
if (diagUpdated) NeedCommit = true;
1704
if (karaoke->enabled && !playingToEnd) {
1705
AudioKaraokeSyllable &syl = karaoke->syllables[karaoke->curSyllable];
1706
player->SetEndPosition(GetSampleAtMS(curStartMS + (syl.start_time+syl.duration)*10));
1707
} else if (!playingToEnd) {
1708
player->SetEndPosition(GetSampleAtX(selEnd));
1711
wxCursor cursor(wxCURSOR_SIZEWE);
1724
if (defCursor) SetCursor(wxNullCursor);
1728
////////////////////////
1729
// Get snap to boundary
1730
int AudioDisplay::GetBoundarySnap(int ms,int rangeX,bool shiftHeld,bool start) {
1732
if (rangeX <= 0) return ms;
1734
// Convert range into miliseconds
1735
int rangeMS = rangeX*samples*1000 / provider->GetSampleRate();
1737
// Keyframe boundaries
1738
wxArrayInt boundaries;
1739
bool snapKey = Options.AsBool(_T("Audio snap to keyframes"));
1740
if (shiftHeld) snapKey = !snapKey;
1741
if (snapKey && VideoContext::Get()->KeyFramesLoaded() && Options.AsBool(_T("Audio Draw Keyframes"))) {
1743
wxArrayInt keyFrames = VideoContext::Get()->GetKeyFrames();
1745
for (unsigned int i=0;i<keyFrames.Count();i++) {
1746
frame = keyFrames[i];
1747
if (!start) frame--;
1748
if (frame < 0) frame = 0;
1749
keyMS = VFR_Output.GetTimeAtFrame(frame,start);
1750
//if (start) keyX++;
1751
if (GetXAtMS(keyMS) >= 0 && GetXAtMS(keyMS) < w) boundaries.Add(keyMS);
1755
// Other subtitles' boundaries
1756
int inactiveType = Options.AsInt(_T("Audio Inactive Lines Display Mode"));
1757
bool snapLines = Options.AsBool(_T("Audio snap to other lines"));
1758
if (shiftHeld) snapLines = !snapLines;
1759
if (snapLines && (inactiveType == 1 || inactiveType == 2)) {
1761
int shadeX1,shadeX2;
1762
int shadeFrom,shadeTo;
1765
if (inactiveType == 1) {
1766
shadeFrom = this->line_n-1;
1767
shadeTo = shadeFrom+1;
1771
shadeTo = grid->GetRows();
1774
for (int j=shadeFrom;j<shadeTo;j++) {
1775
if (j == line_n) continue;
1776
shade = grid->GetDialogue(j);
1780
shadeX1 = GetXAtMS(shade->Start.GetMS());
1781
shadeX2 = GetXAtMS(shade->End.GetMS());
1782
if (shadeX1 >= 0 && shadeX1 < w) boundaries.Add(shade->Start.GetMS());
1783
if (shadeX2 >= 0 && shadeX2 < w) boundaries.Add(shade->End.GetMS());
1788
// See if ms falls within range of any of them
1789
int minDist = rangeMS+1;
1791
for (unsigned int i=0;i<boundaries.Count();i++) {
1792
if (abs(ms-boundaries[i]) < minDist) {
1793
bestMS = boundaries[i];
1794
minDist = abs(ms-boundaries[i]);
1798
// Return best match
1804
// SCRUBBING CODE, REMOVED FROM THE FUNCTION ABOVE
1807
bool scrubButton = false && event.ButtonIsDown(wxMOUSE_BTN_MIDDLE);
1808
if (scrubbing && !scrubButton) {
1811
if (HasCapture()) ReleaseMouse();
1815
player->SetProvider(provider);
1816
delete scrubProvider;
1820
if (!scrubbing && scrubButton && provider->GetChannels() == 1) {
1825
// Initialize provider
1827
scrubProvider = new StreamAudioProvider();
1828
scrubProvider->SetParams(provider->GetChannels(),provider->GetSampleRate(),provider->GetBytesPerSample());
1829
player->SetProvider(scrubProvider);
1832
scrubLastPos = GetSampleAtX(x);
1833
scrubTime = clock();
1834
scrubLastRate = provider->GetSampleRate();
1838
if (scrubbing && scrubButton) {
1840
int64_t exactPos = MAX(0,GetSampleAtX(x));
1841
int curScrubTime = clock();
1842
int scrubDeltaTime = curScrubTime - scrubTime;
1843
bool invert = exactPos < scrubLastPos;
1844
int64_t curScrubPos = exactPos;
1846
if (scrubDeltaTime > 0) {
1848
int rateChange = provider->GetSampleRate()/20;
1849
int curRate = MID(int(scrubLastRate-rateChange),abs(int(exactPos - scrubLastPos)) * CLOCKS_PER_SEC / scrubDeltaTime,int(scrubLastRate+rateChange));
1850
if (abs(curRate-scrubLastRate) < rateChange) curRate = scrubLastRate;
1851
curScrubPos = scrubLastPos + (curRate * scrubDeltaTime / CLOCKS_PER_SEC * (invert ? -1 : 1));
1852
int64_t scrubDelta = curScrubPos - scrubLastPos;
1853
scrubLastRate = curRate;
1855
// Copy data to buffer
1856
if (scrubDelta != 0) {
1858
int bufSize = scrubDeltaTime * scrubProvider->GetSampleRate() / CLOCKS_PER_SEC;
1859
short *buf = new short[bufSize];
1861
// Flag as inverted, if necessary
1862
if (invert) scrubDelta = -scrubDelta;
1864
// Copy data from original provider to temp buffer
1865
short *temp = new short[scrubDelta];
1866
provider->GetAudio(temp,MIN(curScrubPos,scrubLastPos),scrubDelta);
1869
float scale = float(double(scrubDelta) / double(bufSize));
1873
for (int i=0;i<bufSize;i++) {
1876
istart = (int) start;
1877
iend = MIN((int) end,scrubDelta-1);
1878
if (istart == iend) tempfinal = temp[istart] * (end - start);
1880
tempfinal = temp[istart] * (1 + istart - start) + temp[iend] * (end - iend);
1881
for (int j=istart+1;j<iend;j++) tempfinal += temp[i];
1883
buf[i] = tempfinal / scale;
1885
//int len = MIN(bufSize,scrubDelta);
1886
//for (int i=0;i<len;i++) buf[i] = temp[i];
1887
//for (int i=len;i<bufSize;i++) buf[i] = 0;
1893
for (int i=0;i<bufSize/2;i++) {
1895
buf[i] = buf[bufSize-i-1];
1896
buf[bufSize-i-1] = aux;
1900
// Send data to provider
1901
scrubProvider->Append(buf,bufSize);
1902
if (!player->IsPlaying()) player->Play(0,~0ULL);
1907
// Update last pos and time
1908
scrubLastPos = curScrubPos;
1909
scrubTime = curScrubTime;
1920
void AudioDisplay::OnSize(wxSizeEvent &event) {
1922
GetClientSize(&w,&h);
1923
h -= Options.AsBool(_T("Audio Draw Timeline")) ? 20 : 0;
1928
UpdatePosition(PositionSample / samples);
1939
void AudioDisplay::OnUpdateTimer(wxTimerEvent &event) {
1943
// Get lock and check if it's OK
1944
if (player->GetMutex()) {
1945
wxMutexLocker locker(*player->GetMutex());
1946
if (!locker.IsOk()) return;
1949
if (!player->IsPlaying()) return;
1952
//wxMutexGuiEnter();
1953
wxClientDC dc(this);
1957
if (player->IsPlaying()) {
1958
int64_t curPos = player->GetCurrentPosition();
1959
if (curPos > player->GetStartPosition() && curPos < player->GetEndPosition()) {
1961
int posX = GetXAtSample(curPos);
1962
bool fullDraw = false;
1963
bool centerLock = false;
1964
bool scrollToCursor = Options.AsBool(_T("Audio lock scroll on cursor"));
1966
int goTo = MAX(0,curPos - w*samples/2);
1968
UpdatePosition(goTo,true);
1974
if (scrollToCursor) {
1975
if (posX < 80 || posX > w-80) {
1976
int goTo = MAX(0,curPos - 80*samples);
1978
UpdatePosition(goTo,true);
1988
curpos = GetXAtSample(curPos);
1989
if (curpos >= 0 && curpos < GetClientSize().GetWidth()) {
1991
playbackCursorPos = curpos;
1994
dc.SetPen(wxPen(Options.AsColour(_T("Audio Play cursor"))));
1995
src.SelectObject(*origImage);
1997
//dc.Blit(0,0,w,h,&src,0,0);
1998
dc.DrawLine(curpos,0,curpos,h);
1999
//dc.Blit(0,0,curpos-10,h,&src,0,0);
2000
//dc.Blit(curpos+10,0,w-curpos-10,h,&src,curpos+10,0);
2003
dc.Blit(oldCurPos,0,1,h,&src,oldCurPos,0);
2004
dc.DrawLine(curpos,0,curpos,h);
2010
if (curPos > player->GetEndPosition() + 8192) {
2014
playbackCursorPos = -1;
2018
src.SelectObject(*origImage);
2019
dc.Blit(oldCurPos,0,1,h,&src,oldCurPos,0);
2024
// Restore background
2027
playbackCursorPos = -1;
2031
src.SelectObject(*origImage);
2032
dc.Blit(oldCurPos,0,1,h,&src,oldCurPos,0);
2036
if (oldCurPos < 0) oldCurPos = 0;
2042
void AudioDisplay::OnKeyDown(wxKeyEvent &event) {
2043
int key = event.GetKeyCode();
2045
Hotkeys.SetPressed(key,event.m_metaDown,event.m_altDown,event.m_shiftDown);
2047
Hotkeys.SetPressed(key,event.m_controlDown,event.m_altDown,event.m_shiftDown);
2051
if (Hotkeys.IsPressed(_T("Audio Commit"))) {
2052
CommitChanges(true);
2056
// Accept (SSA's "Grab times")
2057
if (Hotkeys.IsPressed(_T("Audio Commit Alt"))) {
2058
CommitChanges(true);
2062
if (Hotkeys.IsPressed(_T("Audio Commit (Stay)"))) {
2067
if (Hotkeys.IsPressed(_T("Audio Prev Line")) || Hotkeys.IsPressed(_T("Audio Prev Line Alt"))) {
2072
if (Hotkeys.IsPressed(_T("Audio Next Line")) || Hotkeys.IsPressed(_T("Audio Next Line Alt"))) {
2077
if (Hotkeys.IsPressed(_T("Audio Play")) || Hotkeys.IsPressed(_T("Audio Play Alt"))) {
2079
GetTimesSelection(start,end);
2084
if (Hotkeys.IsPressed(_T("Audio Play or Stop"))) {
2085
if (player->IsPlaying()) Stop();
2088
GetTimesSelection(start,end);
2094
if (Hotkeys.IsPressed(_T("Audio Stop"))) {
2099
if (Hotkeys.IsPressed(_T("Audio Karaoke Increase Len"))) {
2100
if (karaoke->enabled) {
2101
bool result = karaoke->SyllableDelta(karaoke->curSyllable,1,0);
2102
if (result) diagUpdated = true;
2106
// Increase length (shift)
2107
if (Hotkeys.IsPressed(_T("Audio Karaoke Increase Len Shift"))) {
2108
if (karaoke->enabled) {
2109
bool result = karaoke->SyllableDelta(karaoke->curSyllable,1,1);
2110
if (result) diagUpdated = true;
2115
if (Hotkeys.IsPressed(_T("Audio Karaoke Decrease Len"))) {
2116
if (karaoke->enabled) {
2117
bool result = karaoke->SyllableDelta(karaoke->curSyllable,-1,0);
2118
if (result) diagUpdated = true;
2122
// Decrease length (shift)
2123
if (Hotkeys.IsPressed(_T("Audio Karaoke Decrease Len Shift"))) {
2124
if (karaoke->enabled) {
2125
bool result = karaoke->SyllableDelta(karaoke->curSyllable,-1,1);
2126
if (result) diagUpdated = true;
2131
if (Hotkeys.IsPressed(_T("Audio Scroll Left"))) {
2132
UpdatePosition(Position-128,false);
2137
if (Hotkeys.IsPressed(_T("Audio Scroll Right"))) {
2138
UpdatePosition(Position+128,false);
2142
// Play first 500 ms
2143
if (Hotkeys.IsPressed(_T("Audio Play First 500ms"))) {
2145
GetTimesSelection(start,end);
2147
if (e > end) e = end;
2152
if (Hotkeys.IsPressed(_T("Audio Play Last 500ms"))) {
2154
GetTimesSelection(start,end);
2156
if (s < start) s = start;
2160
// Play 500 ms before
2161
if (Hotkeys.IsPressed(_T("Audio Play 500ms Before"))) {
2163
GetTimesSelection(start,end);
2164
Play(start-500,start);
2167
// Play 500 ms after
2168
if (Hotkeys.IsPressed(_T("Audio Play 500ms After"))) {
2170
GetTimesSelection(start,end);
2174
// Play to end of file
2175
if (Hotkeys.IsPressed(_T("Audio Play To End"))) {
2177
GetTimesSelection(start,end);
2181
// Play original line
2182
if (Hotkeys.IsPressed(_T("Audio Play Original Line"))) {
2184
GetTimesDialogue(start,end);
2185
SetSelection(start, end);
2190
if (Hotkeys.IsPressed(_T("Audio Add Lead In"))) {
2191
AddLead(true,false);
2195
if (Hotkeys.IsPressed(_T("Audio Add Lead Out"))) {
2196
AddLead(false,true);
2201
diagUpdated = false;
2203
if (Options.AsBool(_T("Audio Autocommit")) && curStartMS <= curEndMS) CommitChanges();
2204
else UpdateImage(true);
2211
void AudioDisplay::ChangeLine(int delta, bool block) {
2213
// Get next line number and make sure it's within bounds
2215
if (block && grid->IsInSelection(line_n)) next = grid->GetLastSelRow()+delta;
2216
else next = line_n+delta;
2218
if (next == -1) next = 0;
2219
if (next == grid->GetRows()) next = grid->GetRows() - 1;
2224
grid->editBox->SetToLine(next);
2225
grid->SelectRow(next);
2226
grid->MakeCellVisible(next,0,true);
2227
if (!dialogue) UpdateImage(true);
2228
else UpdateImage(false);
2236
void AudioDisplay::Next(bool play) {
2238
if (karaoke->enabled) {
2239
int nextSyl = karaoke->curSyllable+1;
2240
bool needsUpdate = true;
2242
// Last syllable; jump to next
2243
if (nextSyl >= (signed int)karaoke->syllables.size()) {
2245
if (line_n == grid->GetRows()-1) return;
2248
int result = wxMessageBox(_("Do you want to commit your changes? If you choose No, they will be discarded."),_("Commit?"),wxYES_NO | wxCANCEL | wxICON_QUESTION);
2249
//int result = wxNO;
2250
if (result == wxYES) {
2253
else if (result == wxCANCEL) {
2254
karaoke->curSyllable = (int)karaoke->syllables.size()-1;
2259
karaoke->curSyllable = 0;
2261
needsUpdate = false;
2265
karaoke->SetSyllable(nextSyl);
2266
if (needsUpdate) Update();
2268
GetTimesSelection(start,end);
2269
if (play) Play(start,end);
2281
void AudioDisplay::Prev(bool play) {
2283
if (karaoke->enabled) {
2284
int nextSyl = karaoke->curSyllable-1;
2285
bool needsUpdate = true;
2287
// First syllable; jump line
2290
if (line_n == 0) return;
2293
int result = wxMessageBox(_("Do you want to commit your changes? If you choose No, they will be discarded."),_("Commit?"),wxYES_NO | wxCANCEL);
2294
if (result == wxYES) {
2297
else if (result == wxCANCEL) {
2298
karaoke->curSyllable = 0;
2302
karaoke->curSyllable = -1;
2304
needsUpdate = false;
2308
karaoke->SetSyllable(nextSyl);
2309
if (needsUpdate) Update();
2311
GetTimesSelection(start,end);
2312
if (play) Play(start,end);
2322
///////////////////////////////
2323
// Gets syllable at x position
2324
int AudioDisplay::GetSyllableAtX(int x) {
2325
if (!karaoke->enabled) return -1;
2326
int ms = GetMSAtX(x);
2327
size_t syllables = karaoke->syllables.size();;
2328
int sylstart,sylend;
2330
// Find a matching syllable
2331
for (size_t i=0;i<syllables;i++) {
2332
sylstart = karaoke->syllables.at(i).start_time*10 + curStartMS;
2333
sylend = karaoke->syllables.at(i).duration*10 + sylstart;
2334
if (ms >= sylstart && ms < sylend) {
2344
void AudioDisplay::OnGetFocus(wxFocusEvent &event) {
2351
void AudioDisplay::OnLoseFocus(wxFocusEvent &event) {
2352
if (hasFocus && loaded) {
2360
//////////////////////////////
2361
// Update time edit controls
2362
bool AudioDisplay::UpdateTimeEditCtrls() {
2363
// Make sure this does NOT get short-circuit evaluation,
2364
// this is why binary OR instead of logical OR is used.
2365
// All three time edits must always be updated.
2367
grid->editBox->StartTime->SetTime(curStartMS,true) |
2368
grid->editBox->EndTime->SetTime(curEndMS,true) |
2369
grid->editBox->Duration->SetTime(curEndMS-curStartMS,true);