음악, 삶, 개발
Arpeggiator Tutorial 본문
코드
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] : 마지막으로 노트 전환에 도달했는지 여부에 관계없이 노트 지속 시간과 관련된 현재 시간을 추적합니다.