Skip to content

Commit 21c8816

Browse files
committed
Macros: Migrate to FramePos
1 parent 099a621 commit 21c8816

17 files changed

+138
-148
lines changed

src/engine/controls/enginecontrol.h

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -41,17 +41,17 @@ class EngineControl : public QObject {
4141
UserSettingsPointer pConfig);
4242
~EngineControl() override;
4343

44-
// Called by EngineBuffer::process every latency period. See the above
45-
// comments for information about guarantees that hold during this call. An
46-
// EngineControl can perform any upkeep operations that are necessary during
47-
// this call.
44+
/// Called by EngineBuffer::process every latency period.
45+
/// See the above comments for information about guarantees that hold during this call.
46+
/// An EngineControl can perform any upkeep operations necessary here.
47+
/// @param dRate current playback rate in audio frames per second
4848
virtual void process(const double dRate,
4949
mixxx::audio::FramePos currentPosition,
5050
const int iBufferSize);
5151

52-
// hintReader allows the EngineControl to provide hints to the reader to
53-
// indicate that the given portion of a song is a potential imminent seek
54-
// target.
52+
/// hintReader allows the EngineControl to provide hints to the reader
53+
/// to indicate that the given portion of a song
54+
/// is a potential imminent seek target.
5555
virtual void hintReader(HintVector* pHintList);
5656

5757
virtual void setEngineMaster(EngineMaster* pEngineMaster);
@@ -66,7 +66,7 @@ class EngineControl : public QObject {
6666
mixxx::audio::FramePos endPosition,
6767
bool enabled);
6868

69-
// Called to collect player features for effects processing.
69+
/// Collect player features for effects processing.
7070
virtual void collectFeatureState(GroupFeatureState* pGroupFeatures) const {
7171
Q_UNUSED(pGroupFeatures);
7272
}
@@ -96,10 +96,10 @@ class EngineControl : public QObject {
9696
}
9797
void seek(double fractionalPosition);
9898
void seekAbs(mixxx::audio::FramePos position);
99-
/// Seek to an exact sample, no quantizing
100-
/// virtual only for testing!
99+
/// Seek to an exact frame, no quantizing
100+
/// virtual only for tests!
101101
virtual void seekExact(mixxx::audio::FramePos position);
102-
// Returns an EngineBuffer to target for syncing. Returns nullptr if none found
102+
/// Return an EngineBuffer to target for syncing. Returns nullptr if none found.
103103
EngineBuffer* pickSyncTarget();
104104

105105
UserSettingsPointer getConfig();

src/engine/controls/macrocontrol.cpp

Lines changed: 24 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -76,28 +76,28 @@ MacroControl::MacroControl(const QString& group, UserSettingsPointer pConfig, in
7676
&MacroControl::slotClear);
7777
}
7878

79-
void MacroControl::process(const double dRate, const double dCurrentSample, const int iBufferSize) {
79+
void MacroControl::process(const double dRate,
80+
mixxx::audio::FramePos currentPosition,
81+
const int iBufferSize) {
8082
Q_UNUSED(dRate);
81-
if (m_queuedJumpTarget >= 0) {
83+
if (m_queuedJumpTarget.value() >= 0) {
8284
// if a cue press doesn't change the position, notifySeek isn't called, thus m_queuedJumpTarget isn't reset
8385
if (getStatus() == Status::Armed) {
8486
// start the recording on a cue press even when there is no jump
85-
notifySeek(dCurrentSample);
87+
notifySeek(currentPosition);
8688
}
87-
m_queuedJumpTarget = -1;
89+
m_queuedJumpTarget = mixxx::audio::FramePos(-1);
8890
}
8991
if (getStatus() != Status::Playing) {
9092
return;
9193
}
92-
double framePos = dCurrentSample / mixxx::kEngineChannelCount;
9394
const MacroAction& nextAction = m_pMacro->getActions().at(m_iNextAction);
94-
double nextActionPos = nextAction.getSourcePosition();
95-
int bufFrames = iBufferSize / 2;
96-
// The process method is called roughly every iBufferSize/2 samples, the
95+
// The process method is called roughly every iBufferSize frames, the
9796
// tolerance range is double that to be safe. It is ahead of the position
9897
// because the seek is executed in the next EngineBuffer process cycle.
99-
if (framePos > nextActionPos - bufFrames && framePos < nextActionPos + bufFrames) {
100-
seekExact(nextAction.getTargetPositionSample());
98+
if (currentPosition > nextAction.getSourcePosition() - iBufferSize &&
99+
currentPosition < nextAction.getSourcePosition() + iBufferSize) {
100+
seekExact(nextAction.getTargetPosition());
101101
m_iNextAction++;
102102
if (m_iNextAction == m_pMacro->size()) {
103103
if (m_pMacro->isLooped()) {
@@ -134,26 +134,24 @@ void MacroControl::trackLoaded(TrackPointer pNewTrack) {
134134
}
135135
}
136136

137-
void MacroControl::notifySeek(double dNewPlaypos) {
138-
if (m_queuedJumpTarget < 0) {
137+
void MacroControl::notifySeek(mixxx::audio::FramePos position) {
138+
if (m_queuedJumpTarget.value() < 0) {
139139
return;
140140
}
141-
double originalDestFramePos = m_queuedJumpTarget;
142-
m_queuedJumpTarget = -1;
141+
auto queuedTarget = m_queuedJumpTarget;
142+
m_queuedJumpTarget = mixxx::audio::FramePos(-1);
143143
if (getStatus() == Status::Armed) {
144144
setStatus(Status::Recording);
145145
}
146146
if (getStatus() != Status::Recording) {
147147
return;
148148
}
149-
double sourceFramePos = getSampleOfTrack().current / mixxx::kEngineChannelCount;
150-
double destFramePos = dNewPlaypos / mixxx::kEngineChannelCount;
151-
double diff = originalDestFramePos - destFramePos;
152-
m_recordedActions.try_emplace(sourceFramePos + diff, originalDestFramePos);
149+
// Account for quantization so the jump stays in-phase but snaps exactly to the original target
150+
m_recordedActions.try_emplace(frameInfo().currentPosition + (queuedTarget - position), queuedTarget);
153151
}
154152

155-
void MacroControl::slotJumpQueued(double samplePos) {
156-
m_queuedJumpTarget = samplePos / mixxx::kEngineChannelCount;
153+
void MacroControl::slotJumpQueued(mixxx::audio::FramePos samplePos) {
154+
m_queuedJumpTarget = samplePos;
157155
}
158156

159157
MacroControl::Status MacroControl::getStatus() const {
@@ -218,13 +216,12 @@ bool MacroControl::stopRecording() {
218216
} else {
219217
// This will still be the position of the previous track when called from trackLoaded
220218
// since trackLoaded is invoked before the SampleOfTrack of the controls is updated.
221-
m_pMacro->setEnd(getSampleOfTrack().current / mixxx::kEngineChannelCount);
219+
m_pMacro->setEnd(frameInfo().currentPosition);
222220
if (m_pMacro->getLabel().isEmpty()) {
223-
// Automatically set the start position in seconds as label if there
224-
// is no user-defined one
225-
double secPos = m_pMacro->getStartSamplePos() /
226-
mixxx::kEngineChannelCount / getSampleOfTrack().rate;
227-
m_pMacro->setLabel(QString::number(secPos, 'f', 2));
221+
// Automatically set the start position in seconds as label
222+
// if there is no user-defined one
223+
auto secPos = m_pMacro->getStart() / frameInfo().sampleRate;
224+
m_pMacro->setLabel(QString::number(secPos.value(), 'f', 2));
228225
}
229226
setStatus(Status::Recorded);
230227
if (m_pMacro->isEnabled()) {
@@ -324,7 +321,7 @@ void MacroControl::slotToggle(double value) {
324321

325322
void MacroControl::slotGotoPlay(double value) {
326323
if (value > 0 && getStatus() > Status::Recording) {
327-
seekExact(m_pMacro->getStartSamplePos());
324+
seekExact(m_pMacro->getStart());
328325
play();
329326
}
330327
}

src/engine/controls/macrocontrol.h

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,10 @@ class MacroControl : public EngineControl {
1414
MacroControl(const QString& group, UserSettingsPointer pConfig, int slot);
1515

1616
void trackLoaded(TrackPointer pNewTrack) override;
17-
void process(const double dRate, const double dCurrentSample, const int iBufferSize) override;
18-
void notifySeek(double dNewPlaypos) override;
17+
void process(const double dRate,
18+
mixxx::audio::FramePos currentPosition,
19+
const int iBufferSize) override;
20+
void notifySeek(mixxx::audio::FramePos position) override;
1921

2022
bool isRecording() const;
2123

@@ -42,7 +44,7 @@ class MacroControl : public EngineControl {
4244
void slotToggle(double value = 1);
4345
void slotClear(double value = 1);
4446

45-
void slotJumpQueued(double samplePos);
47+
void slotJumpQueued(mixxx::audio::FramePos samplePos);
4648

4749
private:
4850
/// Returns whether a new action was recorded
@@ -60,7 +62,7 @@ class MacroControl : public EngineControl {
6062
QString m_controlPattern;
6163

6264
/// The unquantized FramePos of a jump that is yet to be processed
63-
double m_queuedJumpTarget;
65+
mixxx::audio::FramePos m_queuedJumpTarget;
6466

6567
rigtorp::SPSCQueue<MacroAction> m_recordedActions;
6668
QTimer m_updateRecordingTimer;

src/engine/enginebuffer.cpp

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -44,20 +44,20 @@
4444
namespace {
4545
const mixxx::Logger kLogger("EngineBuffer");
4646

47-
// This value is used to make sure the initial seek after loading a track is
48-
// not omitted. Therefore this value must be different for 0.0 or any likely
49-
// value for the main cue
47+
/// Used to ensure the initial seek after loading a track is not omitted.
48+
/// Therefore this value must stray away from 0.0 or any probably value for the main cue.
5049
constexpr auto kInitialPlayPosition =
5150
mixxx::audio::FramePos::fromEngineSamplePos(
5251
std::numeric_limits<double>::lowest());
5352

54-
constexpr double kLinearScalerElipsis =
55-
1.00058; // 2^(0.01/12): changes < 1 cent allows a linear scaler
53+
/// 2^(0.01/12): changes < 1 cent allows a linear scaler
54+
constexpr double kLinearScalerElipsis = 1.00058;
5655

57-
constexpr SINT kSamplesPerFrame = 2; // Engine buffer uses Stereo frames only
56+
/// Engine buffer uses Stereo frames only
57+
constexpr SINT kSamplesPerFrame = 2;
5858

59-
// Rate at which the playpos slider is updated
60-
constexpr int kPlaypositionUpdateRate = 15; // updates per second
59+
/// Updates per second of the playpos slider
60+
constexpr int kPlaypositionUpdateRate = 15;
6161

6262
} // anonymous namespace
6363

@@ -999,8 +999,8 @@ void EngineBuffer::processTrackLocked(
999999
// The way we treat rate inside of EngineBuffer is actually a
10001000
// description of "sample consumption rate" or percentage of samples
10011001
// consumed relative to playing back the track at its native sample
1002-
// rate and normal speed. pitch_adjust does not change the playback
1003-
// rate.
1002+
// rate and normal speed.
1003+
// pitch_adjust does not change the playback rate.
10041004
rate = baserate * speed;
10051005

10061006
// Scaler is up to date now.

src/engine/enginebuffer.h

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -64,13 +64,13 @@ class EngineBuffer : public EngineObject {
6464
SEEK_PHASE = 1u,
6565
/// Bypass Quantization
6666
SEEK_EXACT = 2u,
67-
/// This is an artificial state that happens if an exact seek and a
68-
/// phase seek are scheduled at the same time.
67+
/// This artificial state occurs when an exact seek and a phase seek
68+
/// are scheduled at the same time.
6969
SEEK_EXACT_PHASE = SEEK_PHASE | SEEK_EXACT,
70-
/// #SEEK_PHASE if Quantize enables, otherwise SEEK_EXACT
70+
/// SEEK_PHASE if Quantization is enabled, otherwise SEEK_EXACT
7171
SEEK_STANDARD = 4u,
72-
/// This is an artificial state that happens if a standard seek and a
73-
/// phase seek are scheduled at the same time.
72+
/// This artificial state occurs when a standard seek and a phase seek
73+
/// are scheduled at the same time.
7474
SEEK_STANDARD_PHASE = SEEK_STANDARD | SEEK_PHASE,
7575
};
7676
Q_DECLARE_FLAGS(SeekRequests, SeekRequest);
@@ -185,7 +185,7 @@ class EngineBuffer : public EngineObject {
185185
signals:
186186
void trackLoaded(TrackPointer pNewTrack, TrackPointer pOldTrack);
187187
void trackLoadFailed(TrackPointer pTrack, const QString& reason);
188-
void cueJumpQueued(double samplePos);
188+
void cueJumpQueued(mixxx::audio::FramePos targetPosition);
189189

190190
private slots:
191191
void slotTrackLoading();

src/test/macros/macro_test.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
#include <gtest/gtest.h>
44

55
TEST(MacroTest, SerializeMacroActions) {
6-
QList<MacroAction> actions{MacroAction(0, 1)};
6+
QList<MacroAction> actions{MacroAction(mixxx::audio::FramePos(0), mixxx::audio::FramePos(1))};
77

88
QString filename(QDir::currentPath() % "/src/test/macros/macro_proto");
99
ASSERT_TRUE(QFile::exists(filename));

src/test/macros/macro_test.h

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,16 +11,16 @@ struct TestMacro {
1111
MacroAction action;
1212

1313
TestMacro(double sourceFramePos = 25'000, double targetFramePos = 7'500)
14-
: action(sourceFramePos, targetFramePos) {
14+
: action(mixxx::audio::FramePos(sourceFramePos), mixxx::audio::FramePos(targetFramePos)) {
1515
}
1616

1717
/// Checks that the recorded Macro corresponds to the parameters,
1818
/// including the extra first loop action.
1919
void checkMacroAction(MacroPointer macro) {
2020
ASSERT_EQ(macro->size(), 2);
21-
EXPECT_EQ(macro->getActions().last().getSourcePosition(), action.getSourcePosition());
22-
EXPECT_EQ(macro->getActions().last().getTargetPosition(), action.getTargetPosition());
21+
EXPECT_EQ(macro->getActions().last().getSourcePosition().value(), action.getSourcePosition().value());
22+
EXPECT_EQ(macro->getActions().last().getTargetPosition().value(), action.getTargetPosition().value());
2323
// Loopback action
24-
EXPECT_EQ(macro->getActions().first().getSourcePosition(), action.getTargetPosition());
24+
EXPECT_EQ(macro->getActions().first().getSourcePosition().value(), action.getTargetPosition().value());
2525
}
2626
};

src/test/macros/macrocontrol_test.cpp

Lines changed: 26 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ class MacroControlTest : public MacroControl, public testing::Test {
2020
EXPECT_FALSE(isRecording());
2121
}
2222

23-
MOCK_METHOD1(seekExact, void(double));
23+
MOCK_METHOD1(seekExact, void(mixxx::audio::FramePos));
2424

2525
const TrackPointer pLoadedTrack = Track::newTemporary();
2626
};
@@ -39,37 +39,37 @@ TEST_F(MacroControlTest, RecordSeek) {
3939
EXPECT_TRUE(isRecording());
4040
EXPECT_EQ(getMacro()->getLabel(), " [Recording]");
4141
// Prepare recording
42-
int frameRate = 1'000;
43-
auto seek = [this, frameRate](double position) {
42+
mixxx::audio::SampleRate frameRate(1'000);
43+
auto seek = [this, frameRate](mixxx::audio::FramePos position) {
4444
notifySeek(position);
45-
setCurrentSample(position, 99'000, frameRate);
46-
process(0, position, 2);
45+
setFrameInfo(position, mixxx::audio::FramePos(99'000), frameRate);
46+
process(frameRate, position, 1);
4747
};
48-
seek(0);
48+
seek(mixxx::audio::FramePos(0));
4949
ASSERT_EQ(getStatus(), MacroControl::Status::Armed);
5050

5151
// Initial jump
52-
double startPos = 1'008;
52+
mixxx::audio::FramePos startPos(504);
5353
slotJumpQueued(startPos);
5454
seek(startPos);
5555
// Jump with quantization adjustment
5656
TestMacro testMacro;
57-
double diff = 20;
58-
seek(testMacro.action.getSourcePositionSample() - diff);
59-
slotJumpQueued(testMacro.action.getTargetPositionSample());
60-
seek(testMacro.action.getTargetPositionSample() - diff);
57+
mixxx::audio::FrameDiff_t diff(10);
58+
seek(testMacro.action.getSourcePosition() - diff);
59+
slotJumpQueued(testMacro.action.getTargetPosition());
60+
seek(testMacro.action.getTargetPosition() - diff);
6161

6262
// Stop recording
63-
seek(testMacro.action.getTargetPositionSample());
63+
seek(testMacro.action.getTargetPosition());
6464
EXPECT_CALL(*this, seekExact(startPos));
6565
slotRecord(0);
6666
EXPECT_EQ(getStatus(), MacroControl::Status::Playing);
6767
// Check recording result
6868
testMacro.checkMacroAction(getMacro());
69-
EXPECT_EQ(getMacro()->getActions().first().getTargetPositionSample(), startPos);
69+
EXPECT_EQ(getMacro()->getActions().first().getTargetPosition(), startPos);
7070
EXPECT_TRUE(pLoadedTrack->isDirty());
7171
// Check generated label
72-
EXPECT_EQ(startPos / mixxx::kEngineChannelCount / frameRate, 0.504);
72+
EXPECT_EQ((startPos / frameRate).value(), 0.504);
7373
EXPECT_EQ(getMacro()->getLabel().toStdString(), "0.50");
7474
// Activate
7575
EXPECT_CALL(*this, seekExact(startPos));
@@ -107,16 +107,16 @@ TEST_F(MacroControlTest, ControlObjects) {
107107
ControlProxy(kChannelGroup, "macro_2_enable").set(0);
108108
ControlProxy(kChannelGroup, "macro_2_loop").set(1);
109109
EXPECT_TRUE(getMacro()->isLooped());
110-
slotJumpQueued(0);
111-
notifySeek(0);
110+
slotJumpQueued(mixxx::audio::FramePos(0));
111+
notifySeek(mixxx::audio::FramePos(0));
112112
activate.set(1);
113113
ASSERT_STATUS(MacroControl::Status::Recorded);
114114
EXPECT_EQ(getMacro()->getLabel(), label);
115115
activate.set(1);
116116
ASSERT_STATUS(MacroControl::Status::Playing);
117117

118118
// Restart
119-
EXPECT_CALL(*this, seekExact(0));
119+
EXPECT_CALL(*this, seekExact(mixxx::audio::FramePos(0)));
120120
activate.set(1);
121121
ASSERT_STATUS(MacroControl::Status::Playing);
122122

@@ -131,7 +131,7 @@ TEST_F(MacroControlTest, ControlObjects) {
131131
}
132132

133133
TEST_F(MacroControlTest, LoadTrackAndPlayAndClear) {
134-
MacroAction jumpAction(40'000, 0);
134+
MacroAction jumpAction(mixxx::audio::FramePos(40'000), mixxx::audio::FramePos(0));
135135
TestMacro testMacro;
136136

137137
QString label = QStringLiteral("test");
@@ -146,23 +146,23 @@ TEST_F(MacroControlTest, LoadTrackAndPlayAndClear) {
146146
slotActivate();
147147
EXPECT_EQ(getStatus(), MacroControl::Status::Playing);
148148
EXPECT_CALL(*this, seekExact(jumpAction.getTargetPosition()));
149-
process(0, jumpAction.getSourcePositionSample(), 2);
149+
process(0, jumpAction.getSourcePosition(), 2);
150150
EXPECT_EQ(getStatus(), MacroControl::Status::Recorded);
151151

152152
// LOOP
153153
slotLoop(1);
154-
EXPECT_CALL(*this, seekExact(testMacro.action.getTargetPositionSample()));
154+
EXPECT_CALL(*this, seekExact(testMacro.action.getTargetPosition()));
155155
slotActivate();
156156
slotActivate();
157157
// Jump
158-
EXPECT_CALL(*this, seekExact(jumpAction.getTargetPositionSample()));
159-
process(0, jumpAction.getSourcePositionSample(), 2);
158+
EXPECT_CALL(*this, seekExact(jumpAction.getTargetPosition()));
159+
process(0, jumpAction.getSourcePosition(), 2);
160160
// Loop back
161-
EXPECT_CALL(*this, seekExact(testMacro.action.getTargetPositionSample()));
162-
process(0, testMacro.action.getSourcePositionSample(), 2);
161+
EXPECT_CALL(*this, seekExact(testMacro.action.getTargetPosition()));
162+
process(0, testMacro.action.getSourcePosition(), 2);
163163
// Jump again
164-
EXPECT_CALL(*this, seekExact(jumpAction.getTargetPositionSample()));
165-
process(0, jumpAction.getSourcePositionSample(), 2);
164+
EXPECT_CALL(*this, seekExact(jumpAction.getTargetPosition()));
165+
process(0, jumpAction.getSourcePosition(), 2);
166166
EXPECT_EQ(getStatus(), MacroControl::Status::Playing);
167167

168168
// Stop playing

0 commit comments

Comments
 (0)