wxWidgets - Having problem with audio seeking a little before from the specified position.
by apoorv569 from LinuxQuestions.org on (#5PT5A)
First, sorry for the bad subject/title of the post, I couldn't think for a better title for the post.
I have a application that I'm currently developing, and I'm having a problem, that I'm not able to figure out. The application is for managing and auditioning audio samples, and I'm using wxWidgets as the GUI toolkit. Recently I was trying to implement looping a selected region. I have it working for the most part. The only problem I'm facing is that, say I have 2 points set, A and B, A is at 500 and B at 700 in milliseconds, I am doing a little match to calculate these points to pixel position on the screen to show the playhead and selected region points, the problem is that when my playhead reaches point B, and its time go back to A and play the sample again, to essentially loop, it goes back a little before point A i.e 450 or something for example, and I'm pretty sure my calculations are correct, don't know what is causing this issue. Here is a little GIF showing the problem hopefully this explains.
Here is some of the code related to the problem,
Here I calculate the position for the loop points A and B from pixel position to milliseconds, this is a custom event that I defined to send this information from one class to another.
Code:void WaveformViewer::SendLoopPoints()
{
wxLogDebug("%s Called", __FUNCTION__);
SampleHive::SH_LoopPointsEvent event(SampleHive::SH_EVT_LOOP_POINTS_UPDATED, this->GetId());
event.SetEventObject(this);
Database db(m_InfoBar);
int selected_row = m_Library.GetSelectedRow();
if (selected_row < 0)
return;
wxString selected = m_Library.GetTextValue(selected_row, 1);
std::string path = db.GetSamplePathByFilename(m_DatabaseFilepath, selected.BeforeLast('.').ToStdString());
Tags tags(path);
int length = tags.GetAudioInfo().length;
int panel_width = this->GetSize().GetWidth();
int a = m_AnchorPoint.x, b = m_CurrentPoint.x;
double loopA = ((double)a / panel_width) * length;
double loopB = ((double)b / panel_width) * length;
event.SetLoopPoints({ loopA, loopB });
HandleWindowEvent(event);
wxLogDebug("%s processed event, sending loop points..", __FUNCTION__);
}And I receive this custom event in my MainFrame class, and use these points to seek the audio sample from point A to B to loop between these points.
Code:void MainFrame::OnRecieveLoopPoints(SampleHive::SH_LoopPointsEvent& event)
{
wxLogDebug("%s called and recieved loop points", __FUNCTION__);
std::pair<double, double> loop_points = event.GetLoopPoints();
m_LoopA = wxLongLong(loop_points.first);
m_LoopB = wxLongLong(loop_points.second);
int loopA_min = static_cast<int>((m_LoopA / 60000).GetValue());
int loopA_sec = static_cast<int>(((m_LoopA % 60000) / 1000).GetValue());
int loopB_min = static_cast<int>((m_LoopB / 60000).GetValue());
int loopB_sec = static_cast<int>(((m_LoopB % 60000) / 1000).GetValue());
wxLogDebug(wxString::Format("LoopA: %2i:%02i, LoopB: %2i:%02i",
loopA_min, loopA_sec, loopB_min, loopB_sec));
m_LoopPointAText->SetValue(wxString::Format("%2i:%02i", loopA_min, loopA_sec));
m_LoopPointBText->SetValue(wxString::Format("%2i:%02i", loopB_min, loopB_sec));
wxLogDebug("%s Event processed successfully..", __FUNCTION__);
}Here I start the loop process, this is one of the methods to play the audio sample,
Code:void MainFrame::OnClickPlay(wxCommandEvent& event)
{
bStopped = false;
int selected_row = m_Library->GetSelectedRow();
if (selected_row < 0)
return;
wxString selection = m_Library->GetTextValue(selected_row, 1);
wxString sample_path = GetFilenamePathAndExtension(selection).Path;
if (bLoop && m_LoopPointAButton->GetValue() && m_LoopPointBButton->GetValue())
PlaySample(sample_path.ToStdString(), selection.ToStdString(), true, m_LoopA.ToDouble(), wxFromStart);
else
PlaySample(sample_path.ToStdString(), selection.ToStdString());
}PlaySample() is a function I defined so I can have one place where the wxTimer starts,
Code:void MainFrame::PlaySample(const std::string& filepath, const std::string& sample, bool seek, wxFileOffset where, wxSeekMode mode)
{
wxLogDebug("TIMER STARTING FROM %s", __FUNCTION__);
if (m_MediaCtrl->Load(filepath))
{
if (seek)
m_MediaCtrl->Seek(where, mode);
if (!m_MediaCtrl->Play())
wxLogDebug(_("Error! Cannot play sample."));
PushStatusText(wxString::Format(_("Now playing: %s"), sample), 1);
if (!m_Timer->IsRunning())
m_Timer->Start(20, wxTIMER_CONTINUOUS);
}
else
wxLogDebug(_("Error! Cannot load sample."));
}And here I check if playhead is at point B, if it is go back to point A and play again, this is wxTimer event handler,
Code:void MainFrame::UpdateElapsedTime(wxTimerEvent& event)
{
wxLogDebug("TIMER IS RUNNING..");
wxString duration, position;
wxLongLong llLength, llTell;
llLength = m_MediaCtrl->Length();
int total_min = static_cast<int>((llLength / 60000).GetValue());
int total_sec = static_cast<int>(((llLength % 60000) / 1000).GetValue());
llTell = m_MediaCtrl->Tell();
int current_min = static_cast<int>((llTell / 60000).GetValue());
int current_sec = static_cast<int>(((llTell % 60000) / 1000).GetValue());
duration.Printf(wxT("%2i:%02i"), total_min, total_sec);
position.Printf(wxT("%2i:%02i"), current_min, current_sec);
m_SamplePosition->SetLabel(wxString::Format(wxT("%s/%s"), position.c_str(), duration.c_str()));
m_TopControlsPanel->Refresh();
m_TopWaveformPanel->Refresh();
if (bLoop && m_LoopPointAButton->GetValue() && m_LoopPointBButton->GetValue())
if (static_cast<double>(m_MediaCtrl->Tell()) >= m_LoopB.ToDouble())
m_MediaCtrl->Seek(m_LoopA.ToDouble(), wxFromStart);
}Here is the link to full project if needed- SampleHive
This specific feature that I'm asking for help right now, is currently in the experimental-draw-waveform branch only.
I have a application that I'm currently developing, and I'm having a problem, that I'm not able to figure out. The application is for managing and auditioning audio samples, and I'm using wxWidgets as the GUI toolkit. Recently I was trying to implement looping a selected region. I have it working for the most part. The only problem I'm facing is that, say I have 2 points set, A and B, A is at 500 and B at 700 in milliseconds, I am doing a little match to calculate these points to pixel position on the screen to show the playhead and selected region points, the problem is that when my playhead reaches point B, and its time go back to A and play the sample again, to essentially loop, it goes back a little before point A i.e 450 or something for example, and I'm pretty sure my calculations are correct, don't know what is causing this issue. Here is a little GIF showing the problem hopefully this explains.
Here is some of the code related to the problem,
Here I calculate the position for the loop points A and B from pixel position to milliseconds, this is a custom event that I defined to send this information from one class to another.
Code:void WaveformViewer::SendLoopPoints()
{
wxLogDebug("%s Called", __FUNCTION__);
SampleHive::SH_LoopPointsEvent event(SampleHive::SH_EVT_LOOP_POINTS_UPDATED, this->GetId());
event.SetEventObject(this);
Database db(m_InfoBar);
int selected_row = m_Library.GetSelectedRow();
if (selected_row < 0)
return;
wxString selected = m_Library.GetTextValue(selected_row, 1);
std::string path = db.GetSamplePathByFilename(m_DatabaseFilepath, selected.BeforeLast('.').ToStdString());
Tags tags(path);
int length = tags.GetAudioInfo().length;
int panel_width = this->GetSize().GetWidth();
int a = m_AnchorPoint.x, b = m_CurrentPoint.x;
double loopA = ((double)a / panel_width) * length;
double loopB = ((double)b / panel_width) * length;
event.SetLoopPoints({ loopA, loopB });
HandleWindowEvent(event);
wxLogDebug("%s processed event, sending loop points..", __FUNCTION__);
}And I receive this custom event in my MainFrame class, and use these points to seek the audio sample from point A to B to loop between these points.
Code:void MainFrame::OnRecieveLoopPoints(SampleHive::SH_LoopPointsEvent& event)
{
wxLogDebug("%s called and recieved loop points", __FUNCTION__);
std::pair<double, double> loop_points = event.GetLoopPoints();
m_LoopA = wxLongLong(loop_points.first);
m_LoopB = wxLongLong(loop_points.second);
int loopA_min = static_cast<int>((m_LoopA / 60000).GetValue());
int loopA_sec = static_cast<int>(((m_LoopA % 60000) / 1000).GetValue());
int loopB_min = static_cast<int>((m_LoopB / 60000).GetValue());
int loopB_sec = static_cast<int>(((m_LoopB % 60000) / 1000).GetValue());
wxLogDebug(wxString::Format("LoopA: %2i:%02i, LoopB: %2i:%02i",
loopA_min, loopA_sec, loopB_min, loopB_sec));
m_LoopPointAText->SetValue(wxString::Format("%2i:%02i", loopA_min, loopA_sec));
m_LoopPointBText->SetValue(wxString::Format("%2i:%02i", loopB_min, loopB_sec));
wxLogDebug("%s Event processed successfully..", __FUNCTION__);
}Here I start the loop process, this is one of the methods to play the audio sample,
Code:void MainFrame::OnClickPlay(wxCommandEvent& event)
{
bStopped = false;
int selected_row = m_Library->GetSelectedRow();
if (selected_row < 0)
return;
wxString selection = m_Library->GetTextValue(selected_row, 1);
wxString sample_path = GetFilenamePathAndExtension(selection).Path;
if (bLoop && m_LoopPointAButton->GetValue() && m_LoopPointBButton->GetValue())
PlaySample(sample_path.ToStdString(), selection.ToStdString(), true, m_LoopA.ToDouble(), wxFromStart);
else
PlaySample(sample_path.ToStdString(), selection.ToStdString());
}PlaySample() is a function I defined so I can have one place where the wxTimer starts,
Code:void MainFrame::PlaySample(const std::string& filepath, const std::string& sample, bool seek, wxFileOffset where, wxSeekMode mode)
{
wxLogDebug("TIMER STARTING FROM %s", __FUNCTION__);
if (m_MediaCtrl->Load(filepath))
{
if (seek)
m_MediaCtrl->Seek(where, mode);
if (!m_MediaCtrl->Play())
wxLogDebug(_("Error! Cannot play sample."));
PushStatusText(wxString::Format(_("Now playing: %s"), sample), 1);
if (!m_Timer->IsRunning())
m_Timer->Start(20, wxTIMER_CONTINUOUS);
}
else
wxLogDebug(_("Error! Cannot load sample."));
}And here I check if playhead is at point B, if it is go back to point A and play again, this is wxTimer event handler,
Code:void MainFrame::UpdateElapsedTime(wxTimerEvent& event)
{
wxLogDebug("TIMER IS RUNNING..");
wxString duration, position;
wxLongLong llLength, llTell;
llLength = m_MediaCtrl->Length();
int total_min = static_cast<int>((llLength / 60000).GetValue());
int total_sec = static_cast<int>(((llLength % 60000) / 1000).GetValue());
llTell = m_MediaCtrl->Tell();
int current_min = static_cast<int>((llTell / 60000).GetValue());
int current_sec = static_cast<int>(((llTell % 60000) / 1000).GetValue());
duration.Printf(wxT("%2i:%02i"), total_min, total_sec);
position.Printf(wxT("%2i:%02i"), current_min, current_sec);
m_SamplePosition->SetLabel(wxString::Format(wxT("%s/%s"), position.c_str(), duration.c_str()));
m_TopControlsPanel->Refresh();
m_TopWaveformPanel->Refresh();
if (bLoop && m_LoopPointAButton->GetValue() && m_LoopPointBButton->GetValue())
if (static_cast<double>(m_MediaCtrl->Tell()) >= m_LoopB.ToDouble())
m_MediaCtrl->Seek(m_LoopA.ToDouble(), wxFromStart);
}Here is the link to full project if needed- SampleHive
This specific feature that I'm asking for help right now, is currently in the experimental-draw-waveform branch only.