음악, 삶, 개발

Arpeggiator Tutorial 본문

개발 공부/Juce 공부방

Arpeggiator Tutorial

Lee_____ 2020. 9. 10. 07:51

코드

class Arp : public juce::AudioProcessor {

    public :

        void prepareToPlay(double sampleRate, int maximumExpectedSamplesPerBlock) override {
    
            notes.clear();                          // [1]: First, we empty the SortedSet of MIDI note numbers.
            currentNote = 0;                        // [2]: The currentNote variable temporarily holds the current index for the SortedSet of notes.
            lastNoteValue = -1;                     // [3]: The lastNoteValue variable temporarily holds the previous index to be able to stop the note.
            time = 0;                               // [4]: The time variable keeps track of the note duration with respect to the buffer size and sample rate. (time 변수는 buffer size 및 sample rate와 관련하여 노트 지속 시간을 추적합니다)
            rate = static_cast<float>(sampleRate);  // [5]: The rate stores the current sample rate in a float variable.

        }

        void processBlock(juce::AudioBuffer<float>& buffer, juce::MidiBuffer& midiBuffer) override {
            
            // [6]: To ensure that we deal with a MIDI plugin, assert that there are no audio channels in the audio buffer.
            // jassert(buffer.getNumChannels() == 0);
            
            // [7]: We still retrieve the number of samples in the block from the audio buffer.  
            const int numSamples { buffer.getNumSamples() }; 
            
            // [8]: According to the speed parameter of our user interface and the sample rate, we calculate the note duration in number of samples.
            const float speed {0.5f};                                                                   
            const int noteDuration { static_cast<int>(std::ceil(rate * 0.25f * (0.1f + (1.0f - (speed))))) }; 
            
            // [9]: For every event in the MidiBuffer, we add the note to the SortedSet if the event is a "Note On" and remove the note if the event is a "Note Off".
            for (const juce::MidiMessageMetadata metaData : midiBuffer) {

                const juce::MidiMessage midiMessage { metaData.getMessage() };
                if (midiMessage.isNoteOn()) { notes.add(midiMessage.getNoteNumber()); }
                else if (midiMessage.isNoteOff()) { notes.removeValue(midiMessage.getNoteNumber()); }

            }

            // [10]: We then empty the MidiBuffer to add the single notes back in the buffer one by one in the next step.
            midiBuffer.clear(); 

            // [11]: We check whether the current time with the number of samples in the current block added to it is greater than the note duration. 
            // If it is the case this means that by the end of the current block, 
            // we would reach a note transition and we therefore proceed to modify the MidiBuffer. 
            // Otherwise we keep the MIDI state as is.
            if ((time + numSamples) >= noteDuration) {
                
                // [12]: Calculate the sample offset at which the note transition occurs within the current audio block.
                const int offset { juce::jmax(0, juce::jmin((int)(noteDuration - time), numSamples - 1)) };

                // [13]: If the previous note is still playing, 
                // the lastNoteValue variable is greater than 0 and therefore we need to send a "Note Off" event to stop the note from playing with the correct sample offset. 
                // We then reset the lastNoteValue variable.
                if (lastNoteValue > 0) {

                    midiBuffer.addEvent(juce::MidiMessage::noteOff(1, lastNoteValue), offset);
                    lastNoteValue = -1;

                }

                // [14]: If there are notes to shuffle and play in the SortedSet, 
                // we send a "Note On" event to play the first note in the set after having stored the previous note number and retrieved the next note number.
                if (notes.size() > 0) {

                    currentNote = (currentNote + 1) % notes.size();
                    lastNoteValue = notes[currentNote];
                    midiBuffer.addEvent(juce::MidiMessage::noteOn(1, lastNoteValue, (juce::uint8)127), offset);

                }

            }

            // [15]: Finally we keep track of our current time relative to the note duration whether we reach a note transition or not.
            time = (time + numSamples) % noteDuration;

        }

    private :

        int currentNote;
        int lastNoteValue;
        int time;
        float rate;
        juce::SortedSet<int> notes;

}

설명

[1] : 먼저 SortedSet의 MIDI 음표 번호를 비웁니다.


[2] : currentNote 변수는 메모의 SortedSet에 대한 현재 색인을 임시로 보유합니다. 


[3] : lastNoteValue 변수는 메모를 중지할 수 있도록 이전 색인을 임시로 보유합니다. 


[4] : 시간 변수는 버퍼 크기 및 샘플 속도와 관련하여 노트 지속 시간을 추적합니다. 


[5] : 속도는 현재 샘플 속도를 부동 변수에 저장합니다. 


[6] : 우리가 MIDI 플러그인을 다룰 수 있도록 오디오 버퍼에 오디오 채널이 없다고 주장합니다. 


[7] : 우리는 여전히 오디오 버퍼에서 블록의 샘플 수를 검색합니다. 


[8] : 사용자 인터페이스의 속도 매개 변수와 샘플 속도에 따라 샘플 수로 음표 지속 시간을 계산합니다. 


[9] : MidiBuffer의 모든 이벤트에 대해 이벤트가 "Note On"이면 SortedSet에 노트를 추가하고  

이벤트가 "Note Off"이면 노트를 제거합니다. 


[10] : 그런 다음 MidiBuffer를 비워 다음 단계에서 하나의 음표를 버퍼에 하나씩 다시 추가합니다. 


[11] : 현재 블록에 추가된 샘플 수를 가진 현재 시간이 음표 길이보다 큰지 확인합니다.  

이 경우 현재 블록이 끝날 때까지 노트 전환에 도달하므로 MidiBuffer를 수정합니다.  

그렇지 않으면 MIDI 상태를 그대로 유지합니다. 


[12] : 현재 오디오 블록 내에서 음표 전환이 발생하는 샘플 오프셋을 계산합니다. 


[13] : 이전 음이 여전히 재생 중이면 lastNoteValue 변수가 0보다 크므로, 

올바른 샘플 오프셋으로 음이 재생되는 것을 중지하려면 "Note Off"이벤트를 보내야 합니다.  

그런 다음 lastNoteValue 변수를 재설정합니다. 


[14] : SortedSet에 셔플 하고 재생할 음표가 있는 경우 이전 음표 번호를 저장하고, 

다음 음표 번호를 검색한 후 세트의 첫 번째 음표를 재생하기 위해 "Note On"이벤트를 보냅니다. 


[15] : 마지막으로 노트 전환에 도달했는지 여부에 관계없이 노트 지속 시간과 관련된 현재 시간을 추적합니다.