Skip to content

Commit

Permalink
Merge pull request #1035 from JosepMaJAZ/fix-clip-recording
Browse files Browse the repository at this point in the history
Various fixes and additions related to recording the mix to a file
  • Loading branch information
daschuer committed Nov 6, 2016
2 parents 89ca10e + 3e4902c commit 5257679
Show file tree
Hide file tree
Showing 9 changed files with 220 additions and 88 deletions.
86 changes: 71 additions & 15 deletions src/engine/sidechain/enginerecord.cpp
Expand Up @@ -125,16 +125,26 @@ void EngineRecord::updateFromPreferences() {

bool EngineRecord::metaDataHasChanged()
{
//Originally, m_iMetaDataLife was used so that getCurrentPlayingTrack was called
//less often, because it was calculating it.
//Nowadays (since Mixxx 1.11), it just accesses a map on a thread safe method.
TrackPointer pTrack = PlayerInfo::instance().getCurrentPlayingTrack();
if (!pTrack) {
m_iMetaDataLife = kMetaDataLifeTimeout;
return false;
}

//The counter is kept so that changes back and forth with the faders/crossfader
//(like in scratching or other effects) are not counted as multiple track changes
//in the cue file. A better solution could consist of a signal from PlayerInfo and
//a slot that decides if the changes received are valid or are to be ignored once
//the next process call comes. This could also help improve the time written in the CUE.
if (m_iMetaDataLife < kMetaDataLifeTimeout) {
m_iMetaDataLife++;
return false;
}
m_iMetaDataLife = 0;

TrackPointer pTrack = PlayerInfo::instance().getCurrentPlayingTrack();
if (!pTrack)
return false;

if (m_pCurrentTrack) {
if (!pTrack->getId().isValid() || !m_pCurrentTrack->getId().isValid()) {
if ((pTrack->getArtist() == m_pCurrentTrack->getArtist()) &&
Expand All @@ -160,6 +170,9 @@ void EngineRecord::process(const CSAMPLE* pBuffer, const int iBufferSize) {
if (fileOpen()) {
Event::end("EngineRecord recording");
closeFile(); // Close file and free encoder.
if (m_bCueIsEnabled) {
closeCueFile();
}
emit(isRecording(false, false));
}
} else if (recordingStatus == RECORD_READY) {
Expand Down Expand Up @@ -191,7 +204,43 @@ void EngineRecord::process(const CSAMPLE* pBuffer, const int iBufferSize) {
// An error occurred.
emit(isRecording(false, true));
}
} else if (recordingStatus == RECORD_ON) {
} else if (recordingStatus == RECORD_SPLIT_CONTINUE) {
if (fileOpen()) {
closeFile(); // Close file and free encoder.
if (m_bCueIsEnabled) {
closeCueFile();
}
}
updateFromPreferences(); // Update file location from preferences.
if (openFile()) {
qDebug() << "Splitting to a new file: "<< m_fileName;
m_pRecReady->set(RECORD_ON);
emit(isRecording(true, false)); // will notify the RecordingManager

// Since we just started recording, timeout and clear the metadata.
m_iMetaDataLife = kMetaDataLifeTimeout;
m_pCurrentTrack = TrackPointer();

// clean frames counting and get current sample rate.
m_frames = 0;
m_sampleRate = m_pSamplerate->get();
m_recordedDuration = 0;

if (m_bCueIsEnabled) {
openCueFile();
m_cueTrack = 0;
}
} else { // Maybe the encoder could not be initialized
qDebug() << "Could not open" << m_fileName << "for writing.";
Event::end("EngineRecord recording");
qDebug("Setting record flag to: OFF");
m_pRecReady->slotSet(RECORD_OFF);
// An error occurred.
emit(isRecording(false, true));
}
}

if (recordingStatus == RECORD_ON || recordingStatus == RECORD_SPLIT_CONTINUE) {
// If recording is enabled process audio to compressed or uncompressed data.
if (m_encoding == ENCODING_WAVE || m_encoding == ENCODING_AIFF) {
if (m_pSndfile != NULL) {
Expand All @@ -205,6 +254,16 @@ void EngineRecord::process(const CSAMPLE* pBuffer, const int iBufferSize) {
m_pEncoder->encodeBuffer(pBuffer, iBufferSize);
}
}

//Writing cueLine before updating the time counter since we preffer to be ahead
//rather than late.
if (m_bCueIsEnabled) {
if (metaDataHasChanged()) {
m_cueTrack++;
writeCueLine();
m_cueFile.flush();
}
}

// update frames counting and recorded duration (seconds)
m_frames += iBufferSize / 2;
Expand All @@ -214,15 +273,7 @@ void EngineRecord::process(const CSAMPLE* pBuffer, const int iBufferSize) {
// gets recorded duration and emit signal that will be used
// by RecordingManager to update the label besides start/stop button
if (lastDuration != m_recordedDuration) {
emit(durationRecorded(getRecordedDurationStr()));
}

if (m_bCueIsEnabled) {
if (metaDataHasChanged()) {
m_cueTrack++;
writeCueLine();
m_cueFile.flush();
}
emit(durationRecorded(m_recordedDuration));
}
}
}
Expand Down Expand Up @@ -308,6 +359,10 @@ bool EngineRecord::openFile() {
#endif
if (m_pSndfile) {
sf_command(m_pSndfile, SFC_SET_NORM_FLOAT, NULL, SF_TRUE);
// Warning! Depending on how libsndfile is compiled autoclip may not work.
// Ensure CPU_CLIPS_NEGATIVE and CPU_CLIPS_POSITIVE is setup properly in the build.
sf_command(m_pSndfile, SFC_SET_CLIPPING, NULL, SF_TRUE) ;

// Set meta data
int ret = sf_set_string(m_pSndfile, SF_STR_TITLE, m_baTitle.constData());
if (ret != 0) {
Expand Down Expand Up @@ -370,7 +425,8 @@ bool EngineRecord::openCueFile() {
}

m_cueFile.write(QString("FILE \"%1\" %2%3\n").arg(
QString(m_fileName).replace(QString("\""), QString("\\\"")),
QFileInfo(m_fileName).fileName() //strip path
.replace(QString("\""), QString("\\\"")), // escape doublequote
QString(m_encoding).toUpper(),
m_encoding == ENCODING_WAVE ? "E" : " ").toLatin1());
return true;
Expand Down
2 changes: 1 addition & 1 deletion src/engine/sidechain/enginerecord.h
Expand Up @@ -66,7 +66,7 @@ class EngineRecord : public QObject, public EncoderCallback, public SideChainWor
// only one error can occur: the specified file was unable to be opened for
// writing.
void isRecording(bool recording, bool error);
void durationRecorded(QString duration);
void durationRecorded(quint64 durationInt);

private:
int getActiveTracks();
Expand Down
6 changes: 3 additions & 3 deletions src/library/recording/dlgrecording.cpp
Expand Up @@ -34,8 +34,8 @@ DlgRecording::DlgRecording(QWidget* parent, UserSettingsPointer pConfig,

connect(m_pRecordingManager, SIGNAL(isRecording(bool)),
this, SLOT(slotRecordingEnabled(bool)));
connect(m_pRecordingManager, SIGNAL(bytesRecorded(long)),
this, SLOT(slotBytesRecorded(long)));
connect(m_pRecordingManager, SIGNAL(bytesRecorded(int)),
this, SLOT(slotBytesRecorded(int)));
connect(m_pRecordingManager, SIGNAL(durationRecorded(QString)),
this, SLOT(slotDurationRecorded(QString)));

Expand Down Expand Up @@ -129,7 +129,7 @@ void DlgRecording::slotRecordingEnabled(bool isRecording) {
}

// gets number of recorded bytes and update label
void DlgRecording::slotBytesRecorded(long bytes) {
void DlgRecording::slotBytesRecorded(int bytes) {
double megabytes = bytes / 1048576.0;
m_bytesRecordedStr = QString::number(megabytes,'f',2);
refreshLabel();
Expand Down
2 changes: 1 addition & 1 deletion src/library/recording/dlgrecording.h
Expand Up @@ -36,7 +36,7 @@ class DlgRecording : public QWidget, public Ui::DlgRecording, public virtual Lib
public slots:
void toggleRecording(bool toggle);
void slotRecordingEnabled(bool);
void slotBytesRecorded(long);
void slotBytesRecorded(int);
void refreshBrowseModel();
void slotRestoreSearch();
void slotDurationRecorded(QString durationRecorded);
Expand Down
23 changes: 13 additions & 10 deletions src/preferences/dialog/dlgprefrecord.cpp
Expand Up @@ -37,8 +37,6 @@ DlgPrefRecord::DlgPrefRecord(QWidget* parent, UserSettingsPointer pConfig)
setupUi(this);

// See RECORD_* #defines in defs_recording.h
m_pRecordControl = new ControlProxy(
RECORDING_PREF_KEY, "status", this);

m_pRadioOgg = new QRadioButton("Ogg Vorbis");
m_pRadioMp3 = new QRadioButton(ENCODING_MP3);
Expand Down Expand Up @@ -116,22 +114,27 @@ DlgPrefRecord::DlgPrefRecord(QWidget* parent, UserSettingsPointer pConfig)
this, SLOT(slotChangeSplitSize()));

slotApply();
// Make sure a corrupt config file won't cause us to record constantly.
m_pRecordControl->set(RECORD_OFF);

comboBoxSplitting->addItem(SPLIT_650MB);
comboBoxSplitting->addItem(SPLIT_700MB);
comboBoxSplitting->addItem(SPLIT_1024MB);
comboBoxSplitting->addItem(SPLIT_2048MB);
comboBoxSplitting->addItem(SPLIT_4096MB);
comboBoxSplitting->addItem(SPLIT_60MIN);
comboBoxSplitting->addItem(SPLIT_74MIN);
comboBoxSplitting->addItem(SPLIT_80MIN);
comboBoxSplitting->addItem(SPLIT_120MIN);

QString fileSizeStr = m_pConfig->getValueString(ConfigKey(RECORDING_PREF_KEY, "FileSize"));
int index = comboBoxSplitting->findText(fileSizeStr);
if (index > 0) {
if (index >= 0) {
// Set file split size
comboBoxSplitting->setCurrentIndex(index);
}
// Otherwise 650 MB will be default file split size.
else {
//Use max RIFF size (4GB) as default index, since usually people don't want to split.
comboBoxSplitting->setCurrentIndex(4);
}

// Read CUEfile info
CheckBoxRecordCueFile->setChecked(
Expand Down Expand Up @@ -220,15 +223,15 @@ void DlgPrefRecord::slotRecordPathChange() {
void DlgPrefRecord::slotResetToDefaults() {
m_pRadioWav->setChecked(true);
CheckBoxRecordCueFile->setChecked(false);
// 650MB splitting is the default
comboBoxSplitting->setCurrentIndex(0);
// 4GB splitting is the default
comboBoxSplitting->setCurrentIndex(4);

LineEditTitle->setText("");
LineEditAlbum->setText("");
LineEditAuthor->setText("");

// 6 corresponds to 128kbps (only used by MP3 and Ogg though)
SliderQuality->setValue(6);
// 1 corresponds to 16 bits (WAVE/AIFF)
SliderQuality->setValue(1);
}

// This function updates/refreshes the contents of this dialog.
Expand Down
1 change: 0 additions & 1 deletion src/preferences/dialog/dlgprefrecord.h
Expand Up @@ -61,7 +61,6 @@ class DlgPrefRecord : public DlgPreferencePage, public Ui::DlgPrefRecordDlg {

// Pointer to config object
UserSettingsPointer m_pConfig;
ControlProxy* m_pRecordControl;
bool m_bConfirmOverwrite;
QString fileTypeExtension;
QRadioButton* m_pRadioOgg;
Expand Down
18 changes: 11 additions & 7 deletions src/recording/defs_recording.h
Expand Up @@ -11,21 +11,25 @@
#define RECORD_OFF 0.0
#define RECORD_READY 1.0
#define RECORD_ON 2.0
#define RECORD_SPLIT_CONTINUE 3.0

//File options for preferences Splitting
#define SPLIT_650MB "650 MB (CD)"
#define SPLIT_700MB "700 MB (CD)"
#define SPLIT_1024MB "1 GB"
#define SPLIT_2048MB "2 GB"
#define SPLIT_4096MB "4 GB"
#define SPLIT_60MIN "60 Minutes"
#define SPLIT_74MIN "74 Minutes (CD)"
#define SPLIT_80MIN "80 Minutes (CD)"
#define SPLIT_120MIN "120 Minutes"

// Byte conversions Instead of multiplying megabytes with 1024 to get kilobytes
// I use 1000 Once the recording size has reached there's enough room to add
// Byte conversions. Slightly rounded to leave enough room to add
// closing frames by the encoder. All sizes are in bytes.
#define SIZE_650MB Q_UINT64_C(650000000)
#define SIZE_700MB Q_UINT64_C(750000000)
#define SIZE_1GB Q_UINT64_C(1000000000)
#define SIZE_2GB Q_UINT64_C(2000000000)
#define SIZE_4GB Q_UINT64_C(4000000000)
#define SIZE_650MB Q_UINT64_C(680000000)
#define SIZE_700MB Q_UINT64_C(730000000)
#define SIZE_1GB Q_UINT64_C(1070000000)
#define SIZE_2GB Q_UINT64_C(2140000000)
#define SIZE_4GB Q_UINT64_C(4280000000)

#endif

0 comments on commit 5257679

Please sign in to comment.