#include "Imperfector.h" #include "StdInclude.h" #include "Constants.h" #include #include namespace { void SendNoteOffEvent(std::uint8_t iNoteNumber, std::uint16_t iFrameIndex) { JBOX_ASSERT(iNoteNumber < 128); TJBox_NoteEvent event; event.fNoteNumber = iNoteNumber; event.fVelocity = 0; event.fAtFrameIndex = iFrameIndex; JBox_OutputNoteEvent(event); } /// od tego wychodzić - debbug w reaconie dodaje nuty jeden nuty drugi często. //Tryb synchronizacji tempo- //Drugi tryb bez synchronizacji void SendNoteOnEvent(std::uint8_t iNoteNumber, std::uint8_t iVelocity, std::uint16_t iFrameIndex) { JBOX_ASSERT(iNoteNumber < 128); TJBox_NoteEvent event; event.fNoteNumber = iNoteNumber; event.fVelocity = iVelocity; event.fAtFrameIndex = iFrameIndex; JBox_OutputNoteEvent(event); } std::uint16_t PPQToFrameIndex(TJBox_Float64 iPPQ, TJBox_Float64 iTempo, TJBox_Float64 iSampleRate) { std::uint8_t frameIndex = 0; const TJBox_Float64 beatsPerSecond = iTempo / 60; const TJBox_Float64 numPPQsPerSecond = beatsPerSecond * kPPQResolution; const TJBox_Float64 seconds = iPPQ / numPPQsPerSecond; const TJBox_Float64 frames = seconds * iSampleRate; frameIndex = static_cast(std::max(0, std::min(frames, 63))); JBOX_ASSERT(frameIndex >= 0 && frameIndex < kBatchSize); return frameIndex; } // skumać std::int64_t FindNextBeat(std::int64_t iBeatLength, std::int64_t iMaxBeatPosition) { JBOX_ASSERT(iBeatLength > 0); const std::int64_t absolutePPQ = static_cast((iMaxBeatPosition) / iBeatLength) * iBeatLength; return absolutePPQ; } std::uint8_t ConvertCVToNote(TJBox_Value iCVValue) { const TJBox_Float64 value = JBox_GetNumber(iCVValue) * 127.f + 0.1f; if (value < 0) { return 0; } else if (value > 127) { return 127; } return static_cast(value); } bool IsInRange(std::int64_t iPosition, const TPPQRange& iRange) { if (iPosition >= iRange.first && iPosition < iRange.second) { return true; } return false; } bool IsEmptyRange(const TPPQRange& iRange) { return iRange.first == iRange.second; } #if DEBUG bool IsValidRange(const TPPQRange& iRange) { return iRange.first <= iRange.second; } #endif // DEBUG } CImperfector::CImperfector(TJBox_Float64 iSampleRate) : fIsPlaying(false), fSampleRate(iSampleRate), fLastStringizeAmountNote(kInvalidNoteNumber), fLastStringizeSpeedNote(kInvalidNoteNumber), fLastPosition(0) { fCustomPropertiesRef = JBox_GetMotherboardObjectRef("/custom_properties"); fTransportRef = JBox_GetMotherboardObjectRef("/transport"); fNoteStates = JBox_GetMotherboardObjectRef("/note_states"); fEnvironmenRef = JBox_GetMotherboardObjectRef("/environment"); // CV inputs /*TJBox_ObjectRef numeratorNoteCVRef = JBox_GetMotherboardObjectRef("/cv_inputs/numerator_note_cv"); fNumeratorNoteCVInputRef = JBox_MakePropertyRef(numeratorNoteCVRef, "value"); fNumeratorNoteCVConnectedRef = JBox_MakePropertyRef(numeratorNoteCVRef, "connected");*/ TJBox_ObjectRef stringizeAmountNoteCVRef = JBox_GetMotherboardObjectRef("/cv_inputs/stringizeAmount_note_cv"); fstringizeAmountNoteCVInputRef = JBox_MakePropertyRef(stringizeAmountNoteCVRef, "value"); fstringizeAmountNoteCVConnectedRef = JBox_MakePropertyRef(stringizeAmountNoteCVRef, "connected"); TJBox_ObjectRef stringizeSpeedNoteCVRef = JBox_GetMotherboardObjectRef("/cv_inputs/stringizeSpeed_note_cv"); fstringizeSpeedNoteCVInputRef = JBox_MakePropertyRef(stringizeSpeedNoteCVRef, "value"); fstringizeSpeedNoteCVConnectedRef = JBox_MakePropertyRef(stringizeSpeedNoteCVRef, "connected"); /*TJBox_ObjectRef denominatorNoteCVRef = JBox_GetMotherboardObjectRef("/cv_inputs/denominator_note_cv"); fDenominatorNoteCVInputRef = JBox_MakePropertyRef(denominatorNoteCVRef, "value"); fDenominatorNoteCVConnectedRef = JBox_MakePropertyRef(denominatorNoteCVRef, "connected");*/ } void CImperfector::HandleDiffs(const TJBox_PropertyDiff iPropertyDiffs[], TJBox_UInt32 iDiffCount) { for (TJBox_UInt32 i = 0; i < iDiffCount; ++i) { const TJBox_PropertyDiff& diff = iPropertyDiffs[i]; if (diff.fPropertyRef.fObject == fTransportRef && diff.fPropertyTag == kJBox_TransportPlaying) { fIsPlaying = JBox_GetBoolean(diff.fCurrentValue); } else if (diff.fPropertyRef.fObject == fTransportRef && diff.fPropertyTag == kJBox_TransportLoopEnabled) { fIsLooping = JBox_GetBoolean(diff.fCurrentValue); } else if (diff.fPropertyRef.fObject == fTransportRef && diff.fPropertyTag == kJBox_TransportTempo) { fTempo = JBox_GetNumber(diff.fCurrentValue); } else if (diff.fPropertyRef.fObject == fEnvironmenRef && diff.fPropertyTag == kJBox_EnvironmentPlayerBypassed) { fIsBypassedByHost = JBox_GetBoolean(diff.fCurrentValue); } else if (diff.fPropertyRef.fObject == fCustomPropertiesRef && diff.fPropertyTag == kOnOffTag) { fIsEnabled = JBox_GetBoolean(diff.fCurrentValue); } } } void CImperfector::ForwardNoteEvents(const TJBox_PropertyDiff iPropertyDiffs[], TJBox_UInt32 iDiffCount) { for (TJBox_UInt32 i = 0; i < iDiffCount; ++i) { const TJBox_PropertyDiff& propertyDiff = iPropertyDiffs[i]; if (propertyDiff.fPropertyRef.fObject == fNoteStates) { const TJBox_NoteEvent& noteEvent = JBox_AsNoteEvent(propertyDiff); JBox_OutputNoteEvent(noteEvent); } } } // Zobaczy jak wyglada event od tego każde nacisniecie korzysta z tej funkcji- player po nacisnieciu opoznienie std::uint8_t CImperfector::NoteFromCV(std::uint16_t iDefaultNote,TJBox_PropertyRef iNoteCVConnectedRef, TJBox_PropertyRef iNoteCVInputRef) { if (JBox_GetBoolean(JBox_LoadMOMProperty(iNoteCVConnectedRef))) { const TJBox_Value noteCV = JBox_LoadMOMProperty(iNoteCVInputRef); std::uint8_t newNote = ConvertCVToNote(noteCV); return newNote; } return iDefaultNote; } TJBox_Float64 CImperfector::ComputeBatchLengthPPQ() const { const TJBox_Float64 batchLengthPPQ = (kBatchSize / fSampleRate) * ((fTempo / 60) * kPPQResolution); return batchLengthPPQ; } // Returns two ranges to support looping std::pair CImperfector::ComputePPQRangesForCurrentBatch(TJBox_Float64 iBatchLengthPPQ) const { TPPQRange firstRange; TPPQRange secondRange; // Only used if the main seequencer has looped this batch TJBox_Float64 batchBeginPPQ = JBox_LoadMOMPropertyAsNumber(fTransportRef, kJBox_TransportPlayPos); const TJBox_Float64 batchEndPPQ = batchBeginPPQ + iBatchLengthPPQ; firstRange.first = static_cast(batchBeginPPQ); firstRange.second = static_cast(batchEndPPQ); const TJBox_Float64 absDiff = std::abs(batchBeginPPQ - fLastPosition); if (absDiff <= (iBatchLengthPPQ * 4)) { // Sequencer pos has (probably) not jumped, // adjust start of current batch to the end of previous batch firstRange.first = fLastPosition; } // Handle main sequencer looping if (fIsLooping) { // Did the sequencer loop this batch? const std::int64_t loopEndPPQ = static_cast(JBox_LoadMOMPropertyAsNumber(fTransportRef, kJBox_TransportLoopEndPos)); if (IsInRange(loopEndPPQ, firstRange)) { const std::int64_t loopBeginPPQ = static_cast(JBox_LoadMOMPropertyAsNumber(fTransportRef, kJBox_TransportLoopStartPos)); firstRange.second = loopEndPPQ; secondRange.first = loopBeginPPQ; const TJBox_Float64 secondRangeLength = iBatchLengthPPQ - (loopEndPPQ - batchBeginPPQ); JBOX_ASSERT(secondRangeLength >= 0); secondRange.second = loopBeginPPQ + secondRangeLength; if (IsEmptyRange(firstRange)) { // Edge case, the sequencer looped at the end of the batch, // just swap so that the first range is never empty std::swap(firstRange, secondRange); } } } JBOX_ASSERT(IsValidRange(firstRange)); JBOX_ASSERT(!IsEmptyRange(firstRange) || (IsEmptyRange(firstRange) && IsEmptyRange(secondRange))); JBOX_ASSERT(IsValidRange(secondRange)); return std::make_pair(firstRange, secondRange); } void CImperfector::PlayRange(const TPPQRange& iRange) { // Output the numerator beat if it happens in the current batch { const TJBox_Float64 stringizeAmount = JBox_LoadMOMPropertyAsNumber(fCustomPropertiesRef, kStringizeAmountTag) + 1; const TJBox_Float64 stringizeSpeed = JBox_LoadMOMPropertyAsNumber(fCustomPropertiesRef, kStringizeSpeedTag) + 1; const std::int64_t beatLength = static_cast(kPPQResolution * (stringizeSpeed / stringizeAmount)); const std::int64_t nextBeat = FindNextBeat(beatLength, iRange.second); if (IsInRange(nextBeat, iRange)) { const std::int64_t offsetPPQ = nextBeat - iRange.first; const std::uint16_t frameIndex = PPQToFrameIndex(offsetPPQ, fTempo, fSampleRate); const std::uint8_t note = NoteFromCV(kDefaultNumeratorNote, fstringizeAmountNoteCVConnectedRef, fstringizeAmountNoteCVInputRef); // Send a note off for the previously played note. // This is a naive way of handling note off events to keep this example minimal. if (fLastStringizeAmountNote != kInvalidNoteNumber) { SendNoteOffEvent(fLastStringizeAmountNote, 0); } fLastStringizeAmountNote = note; SendNoteOnEvent(note, kNoteVelocity, frameIndex); } } // Output the denominator beat if it happens in the current batch { const std::int64_t beatLength = kPPQResolution; // Denominator is always on quarter note beats const std::int64_t nextBeat = FindNextBeat(beatLength, iRange.second); if (IsInRange(nextBeat, iRange)) { const std::int64_t offsetPPQ = nextBeat - iRange.first; const std::uint16_t frameIndex = PPQToFrameIndex(offsetPPQ, fTempo, fSampleRate); const std::uint8_t note = NoteFromCV(kDefaultDenominatorNote, fstringizeSpeedNoteCVConnectedRef, fstringizeSpeedNoteCVInputRef); if (fLastStringizeSpeedNote != kInvalidNoteNumber) { SendNoteOffEvent(fLastStringizeSpeedNote, 0); } fLastStringizeSpeedNote = note; SendNoteOnEvent(note, kNoteVelocity, frameIndex); } } fLastPosition = iRange.second; } void CImperfector::StringizeAmount(TJBox_Float64 value, char* output) { { const TJBox_Float64 stringizeAmount = JBox_LoadMOMPropertyAsNumber(fCustomPropertiesRef, kStringizeAmountTag) + 1; } } void CImperfector::RenderBatch(const TJBox_PropertyDiff iPropertyDiffs[], TJBox_UInt32 iDiffCount) { HandleDiffs(iPropertyDiffs, iDiffCount); if (fIsBypassedByHost) { // We don't have to do anything if the player is bypassed by the host return; } if (!fIsEnabled || !fIsPlaying) { if (fLastStringizeAmountNote != kInvalidNoteNumber) { SendNoteOffEvent(fLastStringizeAmountNote, 0); fLastStringizeAmountNote = kInvalidNoteNumber; } if (fLastStringizeSpeedNote != kInvalidNoteNumber) { SendNoteOffEvent(fLastStringizeSpeedNote, 0); fLastStringizeSpeedNote = kInvalidNoteNumber; } // Forward all note events if the player is turned off // or if sequencer is not playing ForwardNoteEvents(iPropertyDiffs, iDiffCount); return; } // This player only adds note events, so we simply // forward all incoming events ForwardNoteEvents(iPropertyDiffs, iDiffCount); const TJBox_Float64 batchLength = ComputeBatchLengthPPQ(); const std::pair ranges = ComputePPQRangesForCurrentBatch(batchLength); PlayRange(ranges.first); if (!IsEmptyRange(ranges.second)) { PlayRange(ranges.second); } }