@@ -83,7 +83,7 @@ void AutomationService::addMidiCcModulation(quint64 automationId, int type, quin
8383 return automationId == existingAutomation.id ();
8484 });
8585 iter != m_automations.midiCc .end ()) {
86- MidiCcAutomation:: ModulationParameters modulation { static_cast <MidiCcAutomation:: ModulationParameters::ModulationType>(type), static_cast <float >(cycles), amplitude, offset, inverted };
86+ ModulationParameters modulation { static_cast <ModulationParameters::ModulationType>(type), static_cast <float >(cycles), amplitude, offset, inverted };
8787 iter->setModulation (modulation);
8888 notifyChangedLines (*iter);
8989 juzzlin::L (TAG).info () << " MIDI CC Modulation added to automation id " << automationId;
@@ -92,6 +92,21 @@ void AutomationService::addMidiCcModulation(quint64 automationId, int type, quin
9292 }
9393}
9494
95+ void AutomationService::addPitchBendModulation (quint64 automationId, int type, quint64 cycles, float amplitude, float offset, bool inverted)
96+ {
97+ if (const auto iter = std::ranges::find_if (m_automations.pitchBend , [&](auto && existingAutomation) {
98+ return automationId == existingAutomation.id ();
99+ });
100+ iter != m_automations.pitchBend .end ()) {
101+ ModulationParameters modulation { static_cast <ModulationParameters::ModulationType>(type), static_cast <float >(cycles), amplitude, offset, inverted };
102+ iter->setModulation (modulation);
103+ notifyChangedLines (*iter);
104+ juzzlin::L (TAG).info () << " Pitch Bend Modulation added to automation id " << automationId;
105+ } else {
106+ juzzlin::L (TAG).error () << " No such automation id to add modulation to: " << automationId;
107+ }
108+ }
109+
95110void AutomationService::deleteMidiCcAutomation (const MidiCcAutomation & automationToDelete)
96111{
97112 if (const auto iter = std::ranges::find_if (m_automations.midiCc , [&](auto && existingAutomation) {
@@ -169,7 +184,8 @@ void AutomationService::updatePitchBendAutomation(const PitchBendAutomation & up
169184 if (const auto oldAutomation = *iter; oldAutomation != updatedAutomation) {
170185 *iter = updatedAutomation;
171186 if (oldAutomation.interpolation () != updatedAutomation.interpolation () || //
172- oldAutomation.enabled () != updatedAutomation.enabled ()) {
187+ oldAutomation.enabled () != updatedAutomation.enabled () ||
188+ oldAutomation.modulation () != updatedAutomation.modulation ()) {
173189 notifyChangedLinesMerged (oldAutomation, updatedAutomation);
174190 }
175191 juzzlin::L (TAG).info () << " Pitch Bend Automation updated: " << updatedAutomation.toString ().toStdString ();
@@ -392,9 +408,9 @@ AutomationService::EventList AutomationService::renderMidiCcToEventsByLine(size_
392408 if (modulation.cycles > 0 .f || modulation.amplitude > 0 .f ) {
393409 const double phase = interpolation.line1 > interpolation.line0 ? static_cast <double >(line - interpolation.line0 ) / (static_cast <double >(interpolation.line1 - interpolation.line0 )) : 0 ;
394410 double modulationValue = 0.0 ;
395- if (modulation.type == MidiCcAutomation:: ModulationParameters::ModulationType::SineWave) {
411+ if (modulation.type == ModulationParameters::ModulationType::SineWave) {
396412 modulationValue = sineModulationValue (modulation, phase);
397- } else if (modulation.type == MidiCcAutomation:: ModulationParameters::ModulationType::Random) {
413+ } else if (modulation.type == ModulationParameters::ModulationType::Random) {
398414 modulationValue = randomModulationValue (automation.id (), modulation, phase);
399415 }
400416 totalModulation = modulationValue * modulation.amplitude / 100.0 ; // Amplitude is a percentage
@@ -411,12 +427,12 @@ AutomationService::EventList AutomationService::renderMidiCcToEventsByLine(size_
411427 return events;
412428}
413429
414- double AutomationService::sineModulationValue (const MidiCcAutomation:: ModulationParameters & modulation, double phase) const
430+ double AutomationService::sineModulationValue (const ModulationParameters & modulation, double phase) const
415431{
416432 return (modulation.inverted ? -1 : 1 ) * std::sin (phase * modulation.cycles * 2 * M_PI);
417433}
418434
419- double AutomationService::randomModulationValue (size_t automationId, const MidiCcAutomation:: ModulationParameters & modulation, double phase) const
435+ double AutomationService::randomModulationValue (size_t automationId, const ModulationParameters & modulation, double phase) const
420436{
421437 const int sampleIndex = static_cast <int >(std::floor (phase * modulation.cycles ));
422438 if (sampleIndex < modulation.cycles ) {
@@ -435,14 +451,31 @@ AutomationService::EventList AutomationService::renderPitchBendToEventsByLine(si
435451 if (automation.enabled ()) {
436452 const auto & location = automation.location ();
437453 const auto & interpolation = automation.interpolation ();
454+ const auto & modulation = automation.modulation ();
438455 if (location.pattern () == pattern && location.track () == track && location.column () == column && line >= interpolation.line0 && line <= interpolation.line1 ) {
439456 Interpolator interpolator {
440457 static_cast <size_t >(interpolation.line0 ),
441458 static_cast <size_t >(interpolation.line1 ),
442459 static_cast <double >(interpolation.value0 ),
443460 static_cast <double >(interpolation.value1 )
444461 };
445- const auto percentage = std::clamp (static_cast <int >(interpolator.getValue (static_cast <size_t >(line))), -100 , 100 );
462+ double interpolatedValue = interpolator.getValue (static_cast <size_t >(line));
463+
464+ double totalModulation = 0.0 ;
465+ if (modulation.cycles > 0 .f || modulation.amplitude > 0 .f ) {
466+ const double phase = interpolation.line1 > interpolation.line0 ? static_cast <double >(line - interpolation.line0 ) / (static_cast <double >(interpolation.line1 - interpolation.line0 )) : 0 ;
467+ double modulationValue = 0.0 ;
468+ if (modulation.type == ModulationParameters::ModulationType::SineWave) {
469+ modulationValue = sineModulationValue (modulation, phase);
470+ } else if (modulation.type == ModulationParameters::ModulationType::Random) {
471+ modulationValue = randomModulationValue (automation.id (), modulation, phase);
472+ }
473+ totalModulation = modulationValue * modulation.amplitude / 100.0 ; // Amplitude is a percentage
474+ }
475+ totalModulation += modulation.offset / 100.0 ;
476+ interpolatedValue += interpolatedValue * totalModulation;
477+
478+ const auto percentage = std::clamp (static_cast <int >(std::round (interpolatedValue)), -100 , 100 );
446479 events.push_back (std::make_shared<Event>(tick, PitchBendData { track, column, static_cast <double >(percentage) }));
447480 }
448481 }
@@ -507,9 +540,9 @@ AutomationService::EventList AutomationService::renderMidiCcToEventsByColumn(siz
507540 if (modulation.cycles > 0 .f || modulation.amplitude > 0 .f ) {
508541 const double phase = interpolation.line1 > interpolation.line0 ? static_cast <double >(line - interpolation.line0 ) / (static_cast <double >(interpolation.line1 - interpolation.line0 )) : 0 ;
509542 double modulationValue = 0.0 ;
510- if (modulation.type == MidiCcAutomation:: ModulationParameters::ModulationType::SineWave) {
543+ if (modulation.type == ModulationParameters::ModulationType::SineWave) {
511544 modulationValue = sineModulationValue (modulation, phase);
512- } else if (modulation.type == MidiCcAutomation:: ModulationParameters::ModulationType::Random) {
545+ } else if (modulation.type == ModulationParameters::ModulationType::Random) {
513546 modulationValue = randomModulationValue (automation.id (), modulation, phase);
514547 }
515548 totalModulation = modulationValue * modulation.amplitude / 100.0 ;
@@ -538,6 +571,7 @@ AutomationService::EventList AutomationService::renderPitchBendToEventsByColumn(
538571 if (automation.enabled ()) {
539572 const auto & location = automation.location ();
540573 const auto & interpolation = automation.interpolation ();
574+ const auto & modulation = automation.modulation ();
541575 if (location.pattern () == pattern && location.track () == track && location.column () == column) {
542576 Interpolator interpolator {
543577 static_cast <size_t >(interpolation.line0 ),
@@ -547,7 +581,23 @@ AutomationService::EventList AutomationService::renderPitchBendToEventsByColumn(
547581 };
548582 std::optional<double > prevValue;
549583 for (size_t line = interpolation.line0 ; line <= interpolation.line1 ; line++) {
550- const auto percentage = std::clamp (static_cast <int >(interpolator.getValue (static_cast <size_t >(line))), -100 , 100 );
584+ double interpolatedValue = interpolator.getValue (static_cast <size_t >(line));
585+
586+ double totalModulation = 0.0 ;
587+ if (modulation.cycles > 0 .f || modulation.amplitude > 0 .f ) {
588+ const double phase = interpolation.line1 > interpolation.line0 ? static_cast <double >(line - interpolation.line0 ) / (static_cast <double >(interpolation.line1 - interpolation.line0 )) : 0 ;
589+ double modulationValue = 0.0 ;
590+ if (modulation.type == ModulationParameters::ModulationType::SineWave) {
591+ modulationValue = sineModulationValue (modulation, phase);
592+ } else if (modulation.type == ModulationParameters::ModulationType::Random) {
593+ modulationValue = randomModulationValue (automation.id (), modulation, phase);
594+ }
595+ totalModulation = modulationValue * modulation.amplitude / 100.0 ;
596+ }
597+ totalModulation += modulation.offset / 100.0 ;
598+ interpolatedValue += interpolatedValue * totalModulation;
599+
600+ const auto percentage = std::clamp (static_cast <int >(std::round (interpolatedValue)), -100 , 100 );
551601 const double minDiff = 200.0 / 16383 ;
552602 if (!prevValue || std::fabs (*prevValue - percentage) > minDiff) {
553603 events.push_back (std::make_shared<Event>(tick + line * ticksPerLine, PitchBendData { track, column, static_cast <double >(percentage) }));
0 commit comments