1 #include "metronomeeffect.h"
2
3 #include <QtDebug>
4
5 #include "metronomeclick.h"
6 #include "util/experiment.h"
7 #include "util/math.h"
8 #include "util/sample.h"
9
10
11 // static
getId()12 QString MetronomeEffect::getId() {
13 return "org.mixxx.effects.metronome";
14 }
15
16 // static
getManifest()17 EffectManifestPointer MetronomeEffect::getManifest() {
18 EffectManifestPointer pManifest(new EffectManifest());
19 pManifest->setId(getId());
20 pManifest->setName(QObject::tr("Metronome"));
21 pManifest->setAuthor("The Mixxx Team");
22 pManifest->setVersion("1.0");
23 pManifest->setDescription(QObject::tr("Adds a metronome click sound to the stream"));
24
25 // Period
26 // The maximum is at 128 + 1 allowing 128 as max value and
27 // enabling us to pause time when the parameter is above
28 EffectManifestParameterPointer period = pManifest->addParameter();
29 period->setId("bpm");
30 period->setName(QObject::tr("BPM"));
31 period->setDescription(QObject::tr("Set the beats per minute value of the click sound"));
32 period->setControlHint(EffectManifestParameter::ControlHint::KNOB_LOGARITHMIC);
33 period->setSemanticHint(EffectManifestParameter::SemanticHint::UNKNOWN);
34 period->setUnitsHint(EffectManifestParameter::UnitsHint::UNKNOWN);
35 period->setMinimum(40);
36 period->setDefault(120);
37 period->setMaximum(208);
38
39
40 // Period unit
41 EffectManifestParameterPointer periodUnit = pManifest->addParameter();
42 periodUnit->setId("sync");
43 periodUnit->setName(QObject::tr("Sync"));
44 periodUnit->setDescription(QObject::tr("Synchronizes the BPM with the track if it can be retrieved"));
45 periodUnit->setControlHint(EffectManifestParameter::ControlHint::TOGGLE_STEPPING);
46 periodUnit->setSemanticHint(EffectManifestParameter::SemanticHint::UNKNOWN);
47 periodUnit->setUnitsHint(EffectManifestParameter::UnitsHint::UNKNOWN);
48 periodUnit->setDefault(1);
49 periodUnit->setMinimum(0);
50 periodUnit->setMaximum(1);
51
52 return pManifest;
53 }
54
MetronomeEffect(EngineEffect * pEffect)55 MetronomeEffect::MetronomeEffect(EngineEffect* pEffect)
56 : m_pBpmParameter(pEffect->getParameterById("bpm")),
57 m_pSyncParameter(pEffect->getParameterById("sync")) {
58 }
59
~MetronomeEffect()60 MetronomeEffect::~MetronomeEffect() {
61 }
62
processChannel(const ChannelHandle & handle,MetronomeGroupState * pGroupState,const CSAMPLE * pInput,CSAMPLE * pOutput,const mixxx::EngineParameters & bufferParameters,const EffectEnableState enableState,const GroupFeatureState & groupFeatures)63 void MetronomeEffect::processChannel(
64 const ChannelHandle& handle,
65 MetronomeGroupState* pGroupState,
66 const CSAMPLE* pInput, CSAMPLE* pOutput,
67 const mixxx::EngineParameters& bufferParameters,
68 const EffectEnableState enableState,
69 const GroupFeatureState& groupFeatures) {
70 Q_UNUSED(handle);
71 Q_UNUSED(pInput);
72
73 MetronomeGroupState* gs = pGroupState;
74
75 if (enableState == EffectEnableState::Disabled) {
76 gs->m_framesSinceClickStart = 0;
77 return;
78 }
79
80 SINT clickSize = kClickSize44100;
81 const CSAMPLE* click = kClick44100;
82 if (bufferParameters.sampleRate() >= 96000) {
83 clickSize = kClickSize96000;
84 click = kClick96000;
85 } else if (bufferParameters.sampleRate() >= 48000) {
86 clickSize = kClickSize48000;
87 click = kClick48000;
88 }
89
90 SINT maxFrames;
91 if (m_pSyncParameter->toBool() && groupFeatures.has_beat_length_sec) {
92 maxFrames = static_cast<SINT>(
93 bufferParameters.sampleRate() * groupFeatures.beat_length_sec);
94 if (groupFeatures.has_beat_fraction) {
95 const auto currentFrame = static_cast<SINT>(
96 maxFrames * groupFeatures.beat_fraction);
97 if (maxFrames > clickSize &&
98 currentFrame > clickSize &&
99 currentFrame < maxFrames - clickSize &&
100 gs->m_framesSinceClickStart > clickSize) {
101 // plays a single click on low speed
102 gs->m_framesSinceClickStart = currentFrame;
103 }
104 }
105 } else {
106 maxFrames = static_cast<SINT>(
107 bufferParameters.sampleRate() * 60 / m_pBpmParameter->value());
108 }
109
110 SampleUtil::copy(pOutput, pInput, bufferParameters.samplesPerBuffer());
111
112 if (gs->m_framesSinceClickStart < clickSize) {
113 // still in click region, write remaining click frames.
114 const SINT copyFrames =
115 math_min(bufferParameters.framesPerBuffer(),
116 clickSize - gs->m_framesSinceClickStart);
117 SampleUtil::addMonoToStereo(pOutput, &click[gs->m_framesSinceClickStart],
118 copyFrames);
119 }
120
121 gs->m_framesSinceClickStart += bufferParameters.framesPerBuffer();
122
123 if (gs->m_framesSinceClickStart > maxFrames) {
124 // overflow, all overflowed frames are the start of a new click sound
125 gs->m_framesSinceClickStart -= maxFrames;
126 while (gs->m_framesSinceClickStart > bufferParameters.framesPerBuffer()) {
127 // loop into a valid region, this happens if the maxFrames was lowered
128 gs->m_framesSinceClickStart -= bufferParameters.framesPerBuffer();
129 }
130
131 // gs->m_framesSinceClickStart matches now already at the first frame
132 // of next pOutput.
133 const unsigned int outputOffset =
134 bufferParameters.framesPerBuffer() - gs->m_framesSinceClickStart;
135 const unsigned int copyFrames =
136 math_min(gs->m_framesSinceClickStart, clickSize);
137 SampleUtil::addMonoToStereo(&pOutput[outputOffset * 2], click,
138 copyFrames);
139 }
140 }
141