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