// allocated track: // 1. same LastNote if note not in pattern // 2. first free with no note in pattern // 3. first free // 4. track with oldest high note (C6+) // 5. track with oldest mid note (C3+) // 6. track with oldest note int mi::AllocateTrack(CSequence *pseq, int note) { for (int c = 0; c < numTracks; c++) { if (Tracks[c].LastNote == note) { if (pseq != NULL) { byte *pdata = (byte *)pCB->GetPlayingRow(pseq, 2, c); if (*pdata == NOTE_NO) return c; } } } int best = -1; for (c = 0; c < numTracks; c++) { if (Tracks[c].Voices[Tracks[c].FreeVoice^1].State == CVoice::inactive) { if (pseq != NULL) { byte *pdata = (byte *)pCB->GetPlayingRow(pseq, 2, c); if (*pdata == NOTE_NO) return c; } if (best == -1) best = c; } } if (best != -1) return best; // if we got here it means all voices are active int oldt = -1; // check high for (c = 0; c < numTracks; c++) { if (Tracks[c].LastNote >= ((6 << 4) + 1)) // >= C6 { int at = Tracks[c].Voices[Tracks[c].FreeVoice^1].ActiveTime; if (at > oldt) { oldt = at; best = c; } } } if (best != -1) return best; // check mid for (c = 0; c < numTracks; c++) { if (Tracks[c].LastNote >= ((3 << 4) + 1)) // >= C3 { int at = Tracks[c].Voices[Tracks[c].FreeVoice^1].ActiveTime; if (at > oldt) { oldt = at; best = c; } } } if (best != -1) return best; // check low for (c = 0; c < numTracks; c++) { int at = Tracks[c].Voices[Tracks[c].FreeVoice^1].ActiveTime; if (at > oldt) { oldt = at; best = c; } } assert(best != -1); return best; } void miex::MidiControlChange(int const ctrl, int const channel, int const value) { // TODO: check channel if (ctrl != 64) // 64 = sustain pedal return; if (value < 64) { CSequence *pseq; int stateflags = pmi->pCB->GetStateFlags(); if (stateflags & SF_PLAYING && stateflags & SF_RECORDING) pseq = pmi->pCB->GetPlayingSequence(pmi->ThisMachine); else pseq = NULL; int notedelay = pmi->pMasterInfo->PosInTick * 24 / pmi->pMasterInfo->SamplesPerTick; pmi->SustainPedal = false; for (int c = 0; c < pmi->numTracks; c++) { if (pmi->Tracks[c].Note != NOTE_NO && pmi->Tracks[c].Sustained) { pmi->Tracks[c].Sustained = false; pmi->MidiNoteOff(c, pseq, notedelay); } } } else { pmi->SustainPedal = true; } } void mi::MidiNoteOff(int c, CSequence *pseq, int notedelay) { Tracks[c].NoteOff(); if (pseq != NULL) { byte *pdata = (byte *)pCB->GetPlayingRow(pseq, 2, c); if (notedelay > 0) { if (pdata[0] != NOTE_NO) { if (pdata[2] == 0x0d) { int ondelay = pdata[3] >> 1; pdata[2] = 0x0b; pdata[3] = (ondelay << 4) | (notedelay >> 1); } else { // ondelay = 0 pdata[2] = 0x0b; pdata[3] = (notedelay >> 1); } } else { pdata[0] = NOTE_OFF; pdata[2] = 0x0d; pdata[3] = (byte)notedelay; } } else { pdata[0] = NOTE_OFF; } } } void mi::MidiNote(int const channel, int const value, int const velocity) { // TODO: check channel if (value / 12 > 9) return; byte n = (((value / 12)-1) << 4) | ((value % 12) + 1); CSequence *pseq; int stateflags = pCB->GetStateFlags(); if (stateflags & SF_PLAYING && stateflags & SF_RECORDING) pseq = pCB->GetPlayingSequence(ThisMachine); else pseq = NULL; int notedelay = pMasterInfo->PosInTick * 24 / pMasterInfo->SamplesPerTick; if (velocity > 0) { int c = AllocateTrack(pseq, n); Tracks[c].Note = n; Tracks[c].Velocity = velocity; Tracks[c].NoteOn(); if (pseq != NULL) { byte *pdata = (byte *)pCB->GetPlayingRow(pseq, 2, c); pdata[0] = n; pdata[1] = velocity; if (notedelay > 0) { pdata[2] = 0x0d; pdata[3] = (byte)notedelay; } } } else { for (int c = 0; c < numTracks; c++) { if (Tracks[c].Note == n) { if (SustainPedal) Tracks[c].Sustained = true; else MidiNoteOff(c, pseq, notedelay); break; } } } }