// 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 #include #include #include #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; hasFocus = (wxWindow::FindFocus() == this); // 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) { // Loaded? if (!loaded || !provider) return; // 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; } // Update samples UpdateSamples(); // 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;xIsLoaded()) { 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;isyllables.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 Refresh(false); } /////////////////////// // 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;jGetDialogue(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;iGetKeyFrames(); 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= 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= 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;ienabled) dc.SetPen(wxPen(Options.AsColour(_T("Audio Waveform Modified")))); else dc.SetPen(wxPen(Options.AsColour(_T("Audio Waveform Selected")))); } for (int64_t i=selStartCap;iGetWidth() != 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 temp1=0,temp2=0; if (karaoke->enabled) { // In karaoke mode the entire dialogue line should be visible instead of just the selected syllable GetTimesDialogue(temp1, temp2); } else { GetTimesSelection(temp1,temp2); } int startPos = GetSampleAtMS(temp1); int endPos = GetSampleAtMS(temp2); int startX = GetXAtMS(temp1); int endX = GetXAtMS(temp2); if (force || startX < 50 || endX > w-50) { if (startX < 50) { // 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 = AudioProviderFactory::GetAudioProvider(file); } #else provider = AudioProviderFactory::GetAudioProvider(file); #endif // Get player wxLogDebug(_T("AudioDisplay::SetFile: get audio player")); player = AudioPlayerFactory::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 = AudioProviderFactory::GetAudioProvider(VideoContext::Get()->videoName, 0); else return; // Get player player = AudioPlayerFactory::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 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); } // Commit ok? if (validCommit) { wxLogDebug(_T("AudioDisplay::CommitChanges: valid commit")); // Reset flags diagUpdated = false; NeedCommit = false; // Update dialogues blockUpdate = true; wxArrayInt sel = grid->GetSelection(); int sels = (int)sel.Count(); AssDialogue *curDiag; for (int i=-1;iGetDialogue(sel[i]); if (curDiag == dialogue) continue; } curDiag->Start.SetMS(curStartMS); curDiag->End.SetMS(curEndMS); if (!karaoke->enabled) { // 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(); } // 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); } // Go to next dontReadTimes = true; Next(); dontReadTimes = false; curStartMS = curEndMS; curEndMS = curStartMS + Options.AsInt(_T("Timing Default Duration")); NeedCommit = true; } 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 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; 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()) { // 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; // 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;isyllables.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 && x != lastX) { 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= 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;jGetDialogue(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;iStop(); 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;iAppend(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) { 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 = 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;isyllables.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); }