bool QSoundSeq::PostLoad() { if (readMode != READMODE_CONVERT_TO_MIDI) return true; // We need to add pitch bend events for vibrato, which is controlled by a software LFO // This is actually a bit tricky because the LFO is running independent of the sequence // tempo. It gets updated (251/4) times a second, always. We will have to convert // ticks in our sequence into absolute elapsed time, which means we also need to keep // track of any tempo events that change the absolute time per tick. vector<MidiEvent*> tempoEvents; vector<MidiTrack*>& miditracks = midi->aTracks; // First get all tempo events, we assume they occur on track 1 for (unsigned int i = 0; i < miditracks[0]->aEvents.size(); i++) { MidiEvent* event = miditracks[0]->aEvents[i]; if (event->GetEventType() == MIDIEVENT_TEMPO) tempoEvents.push_back(event); } // Now for each track, gather all vibrato events, lfo events, pitch bend events and track end events for (unsigned int i = 0; i < miditracks.size(); i++) { vector<MidiEvent*> events(tempoEvents); MidiTrack* track = miditracks[i]; int channel = this->aTracks[i]->channel; for (unsigned int j = 0; j < track->aEvents.size(); j++) { MidiEvent* event = miditracks[i]->aEvents[j]; MidiEventType type = event->GetEventType(); if (type == MIDIEVENT_MARKER || type == MIDIEVENT_PITCHBEND || type == MIDIEVENT_ENDOFTRACK) events.push_back(event); } // We need to sort by priority so that vibrato events get ordered correctly. Since they cause the // pitch bend range to be set, they need to occur before pitchbend events. stable_sort(events.begin(), events.end(), PriorityCmp()); //Sort all the events by priority stable_sort(events.begin(), events.end(), AbsTimeCmp()); //Sort all the events by absolute time, so that delta times can be recorded correctly // And now we actually add vibrato and pitch bend events const uint32_t ppqn = GetPPQN(); // pulses (ticks) per quarter note const uint32_t mpLFOt = (uint32_t)((1/(251/4.0)) * 1000000); // microseconds per LFO tick uint32_t mpqn = 500000; // microseconds per quarter note - 120 bpm default uint32_t mpt = mpqn / ppqn; // microseconds per MIDI tick short pitchbend = 0; // pitch bend in cents int pitchbendRange = 200; // pitch bend range in cents default 2 semitones double vibrato = 0; // vibrato depth in cents uint16_t tremelo = 0; // tremelo depth. we divide this value by 0x10000 to get percent amplitude attenuation uint16_t lfoRate = 0; // value added to lfo env every lfo tick uint32_t lfoVal = 0; // LFO envelope value. 0 - 0xFFFFFF . Effective envelope range is -0x1000000 to +0x1000000 int lfoStage = 0; // 0 = rising from mid, 1 = falling from peak, 2 = falling from mid 3 = rising from bottom short lfoCents = 0; // cents adjustment from most recent LFO val excluding pitchbend. long effectiveLfoVal = 0; //bool bLfoRising = true;; // is LFO rising or falling? uint32_t startAbsTicks = 0; // The MIDI tick time to start from for a given vibrato segment size_t numEvents = events.size(); for (size_t j = 0; j < numEvents; j++) { MidiEvent* event = events[j]; uint32_t curTicks = event->AbsTime; //current absolute ticks if (curTicks > 0 /*&& (vibrato > 0 || tremelo > 0)*/ && startAbsTicks < curTicks) { // if we're starting a fresh vibrato segment, let's start the LFO env at 0 and set it to rise /*if (prevVibrato == 0 && prevTremelo == 0) { lfoVal = 0; lfoStage = 0; }*/ long segmentDurTicks = curTicks - startAbsTicks; double segmentDur = segmentDurTicks * mpt; // duration of this segment in micros double lfoTicks = segmentDur / (double)mpLFOt; double numLfoPhases = (lfoTicks * (double)lfoRate) / (double)0x20000; //double midiTicksPerPhase = (curTicks-startAbsTicks) / numLfoPhases; //double centsPerMidiTick = vibrato / midiTicksPerPhase; double lfoRatePerMidiTick = (numLfoPhases * 0x20000) / (double)segmentDurTicks; const uint8_t tickRes = 16; uint32_t lfoRatePerLoop = (uint32_t)((tickRes * lfoRatePerMidiTick) * 256); for (int t = 0; t < segmentDurTicks; t += tickRes) { lfoVal += lfoRatePerLoop; if (lfoVal > 0xFFFFFF) { lfoVal -= 0x1000000; lfoStage = (lfoStage+1) % 4; } effectiveLfoVal = lfoVal; if (lfoStage == 1) effectiveLfoVal = 0x1000000 - lfoVal; else if (lfoStage == 2) effectiveLfoVal = -((long)lfoVal); else if (lfoStage == 3) effectiveLfoVal = -0x1000000 + lfoVal; double lfoPercent = (effectiveLfoVal / (double)0x1000000); if (vibrato > 0) { lfoCents = (short)(lfoPercent * vibrato); track->InsertPitchBend(channel, (short)(((lfoCents + pitchbend) / (double)pitchbendRange) * 8192), startAbsTicks + t); } if (tremelo > 0) { uint8_t expression = ConvertPercentAmpToStdMidiVal((0x10000 - (tremelo*abs(lfoPercent))) / (double)0x10000); track->InsertExpression(channel, expression, startAbsTicks + t); } } // TODO add adjustment for segmentDurTicks % tickRes } switch(event->GetEventType()) { case MIDIEVENT_TEMPO: { TempoEvent* tempoevent = (TempoEvent*)event; mpqn = tempoevent->microSecs; mpt = mpqn / ppqn; } break; case MIDIEVENT_ENDOFTRACK: break; case MIDIEVENT_MARKER: { MarkerEvent* marker = (MarkerEvent*)event; if (marker->name == "vibrato") { vibrato = vibrato_depth_table[marker->databyte1] * (100/256.0); //pitchbendRange = max(200, (vibrato + 50)); //50 cents to allow for pitchbend values, which range -50/+50 pitchbendRange = (int)max(200, ceil((vibrato+50)/100.0)*100); //+50 cents to allow for pitchbend values, which range -50/+50 track->InsertPitchBendRange(channel, pitchbendRange/100, pitchbendRange%100, curTicks); lfoCents = (short)((effectiveLfoVal / (double)0x1000000) * vibrato); if (curTicks > 0) track->InsertPitchBend(channel, (short)(((lfoCents + pitchbend) / (double)pitchbendRange) * 8192), curTicks); } else if (marker->name == "tremelo") { tremelo = tremelo_depth_table[marker->databyte1]; if (tremelo == 0) track->InsertExpression(channel, 127, curTicks); } else if (marker->name == "lfo") { lfoRate = lfo_rate_table[marker->databyte1]; } else if (marker->name == "resetlfo") { if (marker->databyte1 != 1) break; lfoVal = 0; effectiveLfoVal = 0; lfoStage = 0; lfoCents = 0; if (vibrato > 0) track->InsertPitchBend(channel, (short)(((0 + pitchbend) / (double)pitchbendRange) * 8192), curTicks); if (tremelo > 0) track->InsertExpression(channel, 127, curTicks); } else if (marker->name == "pitchbend") { pitchbend = (short)(((char)marker->databyte1 / 256.0) * 100); //stringstream stream; //stream << "cents: " << cents << " finalval: " << (cents / (double)pitchbendRange) * 8192 << "\n"; //ATLTRACE(stream.str().c_str()); track->InsertPitchBend(channel, (short)(((lfoCents + pitchbend) / (double)pitchbendRange) * 8192), curTicks); } } break; } startAbsTicks = curTicks; } } return VGMSeq::PostLoad(); }