1 #pragma once
2 
3 #include <gmock/gmock.h>
4 #include <gtest/gtest.h>
5 
6 #include <QTest>
7 #include <QtDebug>
8 
9 #include "control/controlobject.h"
10 #include "effects/effectsmanager.h"
11 #include "engine/bufferscalers/enginebufferscale.h"
12 #include "engine/channels/enginechannel.h"
13 #include "engine/channels/enginedeck.h"
14 #include "engine/controls/ratecontrol.h"
15 #include "engine/enginebuffer.h"
16 #include "engine/enginemaster.h"
17 #include "engine/sync/enginesync.h"
18 #include "mixer/deck.h"
19 #include "mixer/playerinfo.h"
20 #include "mixer/previewdeck.h"
21 #include "mixer/sampler.h"
22 #include "preferences/usersettings.h"
23 #include "test/mixxxtest.h"
24 #include "track/track.h"
25 #include "util/defs.h"
26 #include "util/memory.h"
27 #include "util/sample.h"
28 #include "util/types.h"
29 #include "waveform/guitick.h"
30 #include "waveform/visualsmanager.h"
31 
32 using ::testing::Return;
33 using ::testing::_;
34 
35 // Subclass of EngineMaster that provides access to the master buffer object
36 // for comparison.
37 class TestEngineMaster : public EngineMaster {
38   public:
TestEngineMaster(UserSettingsPointer _config,const QString & group,EffectsManager * pEffectsManager,ChannelHandleFactoryPointer pChannelHandleFactory,bool bEnableSidechain)39     TestEngineMaster(UserSettingsPointer _config,
40             const QString& group,
41             EffectsManager* pEffectsManager,
42             ChannelHandleFactoryPointer pChannelHandleFactory,
43             bool bEnableSidechain)
44             : EngineMaster(_config,
45                       group,
46                       pEffectsManager,
47                       pChannelHandleFactory,
48                       bEnableSidechain) {
49         m_pMasterEnabled->forceSet(1);
50         m_pHeadphoneEnabled->forceSet(1);
51         m_pBoothEnabled->forceSet(1);
52     }
53 
masterBuffer()54     CSAMPLE* masterBuffer() {
55         return m_pMaster;
56     }
57 };
58 
59 class BaseSignalPathTest : public MixxxTest {
60   protected:
BaseSignalPathTest()61     BaseSignalPathTest() {
62         m_pGuiTick = std::make_unique<GuiTick>();
63         m_pChannelHandleFactory = std::make_shared<ChannelHandleFactory>();
64         m_pNumDecks = new ControlObject(ConfigKey("[Master]", "num_decks"));
65         m_pEffectsManager = new EffectsManager(NULL, config(), m_pChannelHandleFactory);
66         m_pVisualsManager = new VisualsManager();
67         m_pEngineMaster = new TestEngineMaster(m_pConfig, "[Master]",
68                                                m_pEffectsManager, m_pChannelHandleFactory,
69                                                false);
70 
71         m_pMixerDeck1 = new Deck(NULL, m_pConfig, m_pEngineMaster, m_pEffectsManager,
72                 m_pVisualsManager, EngineChannel::CENTER, m_sGroup1);
73         m_pMixerDeck2 = new Deck(NULL, m_pConfig, m_pEngineMaster, m_pEffectsManager,
74                 m_pVisualsManager, EngineChannel::CENTER, m_sGroup2);
75         m_pMixerDeck3 = new Deck(NULL, m_pConfig, m_pEngineMaster, m_pEffectsManager,
76                 m_pVisualsManager, EngineChannel::CENTER, m_sGroup3);
77 
78         m_pChannel1 = m_pMixerDeck1->getEngineDeck();
79         m_pChannel2 = m_pMixerDeck2->getEngineDeck();
80         m_pChannel3 = m_pMixerDeck3->getEngineDeck();
81         m_pPreview1 = new PreviewDeck(NULL, m_pConfig, m_pEngineMaster, m_pEffectsManager,
82                 m_pVisualsManager, EngineChannel::CENTER, m_sPreviewGroup);
83         ControlObject::set(ConfigKey(m_sPreviewGroup, "file_bpm"), 2.0);
84 
85         // TODO(owilliams) Tests fail with this turned on because EngineSync is syncing
86         // to this sampler.  FIX IT!
87         // m_pSampler1 = new Sampler(NULL, m_pConfig,
88         //                           m_pEngineMaster, m_pEffectsManager,
89         //                           EngineChannel::CENTER, m_sSamplerGroup);
90         // ControlObject::getControl(ConfigKey(m_sSamplerGroup, "file_bpm"))->set(2.0);
91 
92         addDeck(m_pChannel1);
93         addDeck(m_pChannel2);
94         addDeck(m_pChannel3);
95 
96         m_pEngineSync = m_pEngineMaster->getEngineSync();
97         ControlObject::set(ConfigKey("[Master]", "enabled"), 1.0);
98 
99         PlayerInfo::create();
100     }
101 
~BaseSignalPathTest()102     ~BaseSignalPathTest() override {
103         delete m_pMixerDeck1;
104         delete m_pMixerDeck2;
105         delete m_pMixerDeck3;
106         m_pChannel1 = NULL;
107         m_pChannel2 = NULL;
108         m_pChannel3 = NULL;
109         m_pEngineSync = NULL;
110         delete m_pPreview1;
111 
112         // Deletes all EngineChannels added to it.
113         delete m_pEngineMaster;
114         delete m_pEffectsManager;
115         delete m_pVisualsManager;
116         delete m_pNumDecks;
117         PlayerInfo::destroy();
118     }
119 
addDeck(EngineDeck * pDeck)120     void addDeck(EngineDeck* pDeck) {
121         ControlObject::set(ConfigKey(pDeck->getGroup(), "master"), 1.0);
122         ControlObject::set(ConfigKey(pDeck->getGroup(), "rate_dir"), kDefaultRateDir);
123         ControlObject::set(ConfigKey(pDeck->getGroup(), "rateRange"), kDefaultRateRange);
124         m_pNumDecks->set(m_pNumDecks->get() + 1);
125     }
126 
loadTrack(Deck * pDeck,TrackPointer pTrack)127     void loadTrack(Deck* pDeck, TrackPointer pTrack) {
128         pDeck->slotLoadTrack(pTrack, false);
129 
130         // Wait for the track to load.
131         ProcessBuffer();
132         EngineDeck* pEngineDeck = pDeck->getEngineDeck();
133         while (!pEngineDeck->getEngineBuffer()->isTrackLoaded()) {
134             QTest::qSleep(1); // millis
135         }
136     }
137 
138     // Asserts that the contents of the output buffer matches a reference
139     // data file where each float sample value must be within the delta to pass.
140     // To create a reference file, just run the test. It will fail, but the test
141     // will write out the actual buffers to the testdata/reference_buffers/
142     // directory. Remove the ".actual" extension to create the file the test
143     // will compare against.  On the next run, the test should pass.
144     // Use tools/AudioPlot.py to look at the reference file and make sure it
145     // looks correct.  Each line of the generated file contains the left sample
146     // followed by the right sample.
147     void assertBufferMatchesReference(const CSAMPLE* pBuffer,
148             const int iBufferSize,
149             const QString& reference_title,
150             const double delta = .0001) {
151         QFile f(QDir::currentPath() + "/src/test/reference_buffers/" + reference_title);
152         bool pass = true;
153         int i = 0;
154         // If the file is not there, we will fail and write out the .actual
155         // reference file.
156         if (f.open(QFile::ReadOnly | QFile::Text)) {
157             QTextStream in(&f);
158             // Note: We will only compare as many values as there are in the reference file.
159             for (; i < iBufferSize && !in.atEnd(); i += 2) {
160                 QStringList line = in.readLine().split(',');
161                 if (line.length() != 2) {
162                     qWarning() << "Unexpected line length in reference file";
163                     pass = false;
164                     break;
165                 }
166                 bool ok = false;
167                 const double gold_value0 = line[0].toDouble(&ok);
168                 ASSERT_TRUE(ok);
169                 const double gold_value1 = line[1].toDouble(&ok);
170                 ASSERT_TRUE(ok);
171                 if (fabs(gold_value0 - pBuffer[i]) > delta) {
172                     qWarning() << "Golden check failed at index" << i << ", "
173                                << gold_value0 << "vs" << pBuffer[i];
174                     pass = false;
175                 }
176                 if (fabs(gold_value1 - pBuffer[i + 1]) > delta) {
177                     qWarning() << "Golden check failed at index" << i + 1 << ", "
178                                << gold_value1 << "vs" << pBuffer[i + 1];
179                     pass = false;
180                 }
181             }
182         }
183         // Fail if either we didn't pass, or the comparison file was empty.
184         if (!pass || i == 0) {
185             QString fname_actual = reference_title + ".actual";
186             qWarning() << "Buffer does not match" << reference_title
187                        << ", actual buffer written to "
188                        << "reference_buffers/" + fname_actual;
189             QFile actual(QDir::currentPath() + "/src/test/reference_buffers/" + fname_actual);
190             ASSERT_TRUE(actual.open(QFile::WriteOnly | QFile::Text));
191             QTextStream out(&actual);
192             for (int i = 0; i < iBufferSize; i += 2) {
193                 out << QString("%1,%2\n").arg(pBuffer[i]).arg(pBuffer[i + 1]);
194             }
195             actual.close();
196             EXPECT_TRUE(false);
197         }
198         f.close();
199     }
200 
getRateSliderValue(double rate)201     double getRateSliderValue(double rate) const {
202         return (rate - 1.0) / kRateRangeDivisor;
203     }
204 
ProcessBuffer()205     void ProcessBuffer() {
206         m_pEngineMaster->process(kProcessBufferSize);
207     }
208 
209     ChannelHandleFactoryPointer m_pChannelHandleFactory;
210     ControlObject* m_pNumDecks;
211     std::unique_ptr<GuiTick> m_pGuiTick;
212     VisualsManager* m_pVisualsManager;
213     EffectsManager* m_pEffectsManager;
214     EngineSync* m_pEngineSync;
215     TestEngineMaster* m_pEngineMaster;
216     Deck *m_pMixerDeck1, *m_pMixerDeck2, *m_pMixerDeck3;
217     EngineDeck *m_pChannel1, *m_pChannel2, *m_pChannel3;
218     PreviewDeck* m_pPreview1;
219 
220     static const QString m_sGroup1;
221     static const QString m_sGroup2;
222     static const QString m_sGroup3;
223     static const QString m_sMasterGroup;
224     static const QString m_sInternalClockGroup;
225     static const QString m_sPreviewGroup;
226     static const QString m_sSamplerGroup;
227     static const double kDefaultRateRange;
228     static const double kDefaultRateDir;
229     static const double kRateRangeDivisor;
230     static const int kProcessBufferSize;
231 };
232 
233 class SignalPathTest : public BaseSignalPathTest {
234   protected:
SignalPathTest()235     SignalPathTest() {
236         const QString kTrackLocationTest = QDir::currentPath() + "/src/test/sine-30.wav";
237         TrackPointer pTrack(Track::newTemporary(kTrackLocationTest));
238 
239         loadTrack(m_pMixerDeck1, pTrack);
240         loadTrack(m_pMixerDeck2, pTrack);
241         loadTrack(m_pMixerDeck3, pTrack);
242     }
243 };
244