Mercurial > audlegacy-plugins
changeset 2220:0cb173caf393
Automated merge with ssh://hg.atheme.org//hg/audacious-plugins
author | Eugene Zagidullin <e.asphyx@gmail.com> |
---|---|
date | Fri, 07 Dec 2007 21:35:28 +0300 |
parents | d7acad3d50e8 (current diff) 6907fc39b53f (diff) |
children | b4a2a7197c05 |
files | |
diffstat | 2 files changed, 1354 insertions(+), 0 deletions(-) [+] |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/modplug/load_mid.cxx Fri Dec 07 21:35:28 2007 +0300 @@ -0,0 +1,1126 @@ +/* + * This program is free software; you can redistribute it and modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the license or (at your + * option) any later version. + * + * Authors: Olivier Lapicque <olivierl@jps.net> +*/ + +////////////////////////////////////////////// +// MIDI loader // +////////////////////////////////////////////// +#include "stdafx.h" +#include "sndfile.h" + +#define MIDI_DRUMCHANNEL 10 +#define MIDI_MAXTRACKS 64 + +UINT gnMidiImportSpeed = 3; +UINT gnMidiPatternLen = 128; + +#pragma pack(1) + +typedef struct MIDIFILEHEADER +{ + CHAR id[4]; // "MThd" = 0x6468544D + DWORD len; // 6 + WORD w1; // 1? + WORD wTrks; // 2? + WORD wDivision; // F0 +} MIDIFILEHEADER; + + +typedef struct MIDITRACKHEADER +{ + CHAR id[4]; // "MTrk" = 0x6B72544D + DWORD len; +} MIDITRACKHEADER; + +static LONG midivolumetolinear(UINT nMidiVolume) +{ + return (nMidiVolume * nMidiVolume << 16) / (127*127); +} + +////////////////////////////////////////////////////////////////////// +// Midi Loader Internal Structures + +#define CHNSTATE_NOTEOFFPENDING 0x0001 + +// MOD Channel State description (current volume, panning, etc...) +typedef struct MODCHANNELSTATE +{ + DWORD flags; // Channel Flags + WORD idlecount; + WORD pitchsrc, pitchdest; // Pitch Bend (current position/new position) + BYTE parent; // Midi Channel parent + BYTE pan; // Channel Panning 0-255 + BYTE note; // Note On # (0=available) +} MODCHANNELSTATE; + +// MIDI Channel State (Midi Channels 0-15) +typedef struct MIDICHANNELSTATE +{ + DWORD flags; // Channel Flags + WORD pitchbend; // Pitch Bend Amount (14-bits unsigned) + BYTE note_on[128]; // If note=on -> MOD channel # + 1 (0 if note=off) + BYTE program; // Channel Midi Program + WORD bank; // 0-16383 + // -- Controllers --------- function ---------- CC# --- range --- init (midi) --- + BYTE pan; // Channel Panning CC10 [0-255] 128 (64) + BYTE expression; // Channel Expression CC11 0-128 128 (127) + BYTE volume; // Channel Volume CC7 0-128 80 (100) + BYTE modulation; // Modulation CC1 0-127 0 + BYTE pitchbendrange;// Pitch Bend Range 64 +} MIDICHANNELSTATE; + +typedef struct MIDITRACK +{ + LPCBYTE ptracks, ptrmax; + DWORD status; + LONG nexteventtime; +} MIDITRACK; + +#pragma pack() + + + +LPCSTR szMidiGroupNames[17] = +{ + "Piano", + "Chromatic Percussion", + "Organ", + "Guitar", + "Bass", + "Strings", + "Ensemble", + "Brass", + "Reed", + "Pipe", + "Synth Lead", + "Synth Pad", + "Synth Effects", + "Ethnic", + "Percussive", + "Sound Effects", + "Percussions" +}; + + +LPCSTR szMidiProgramNames[128] = +{ + // 1-8: Piano + "Acoustic Grand Piano", + "Bright Acoustic Piano", + "Electric Grand Piano", + "Honky-tonk Piano", + "Electric Piano 1", + "Electric Piano 2", + "Harpsichord", + "Clavi", + // 9-16: Chromatic Percussion + "Celesta", + "Glockenspiel", + "Music Box", + "Vibraphone", + "Marimba", + "Xylophone", + "Tubular Bells", + "Dulcimer", + // 17-24: Organ + "Drawbar Organ", + "Percussive Organ", + "Rock Organ", + "Church Organ", + "Reed Organ", + "Accordion", + "Harmonica", + "Tango Accordion", + // 25-32: Guitar + "Acoustic Guitar (nylon)", + "Acoustic Guitar (steel)", + "Electric Guitar (jazz)", + "Electric Guitar (clean)", + "Electric Guitar (muted)", + "Overdriven Guitar", + "Distortion Guitar", + "Guitar harmonics", + // 33-40 Bass + "Acoustic Bass", + "Electric Bass (finger)", + "Electric Bass (pick)", + "Fretless Bass", + "Slap Bass 1", + "Slap Bass 2", + "Synth Bass 1", + "Synth Bass 2", + // 41-48 Strings + "Violin", + "Viola", + "Cello", + "Contrabass", + "Tremolo Strings", + "Pizzicato Strings", + "Orchestral Harp", + "Timpani", + // 49-56 Ensemble + "String Ensemble 1", + "String Ensemble 2", + "SynthStrings 1", + "SynthStrings 2", + "Choir Aahs", + "Voice Oohs", + "Synth Voice", + "Orchestra Hit", + // 57-64 Brass + "Trumpet", + "Trombone", + "Tuba", + "Muted Trumpet", + "French Horn", + "Brass Section", + "SynthBrass 1", + "SynthBrass 2", + // 65-72 Reed + "Soprano Sax", + "Alto Sax", + "Tenor Sax", + "Baritone Sax", + "Oboe", + "English Horn", + "Bassoon", + "Clarinet", + // 73-80 Pipe + "Piccolo", + "Flute", + "Recorder", + "Pan Flute", + "Blown Bottle", + "Shakuhachi", + "Whistle", + "Ocarina", + // 81-88 Synth Lead + "Lead 1 (square)", + "Lead 2 (sawtooth)", + "Lead 3 (calliope)", + "Lead 4 (chiff)", + "Lead 5 (charang)", + "Lead 6 (voice)", + "Lead 7 (fifths)", + "Lead 8 (bass + lead)", + // 89-96 Synth Pad + "Pad 1 (new age)", + "Pad 2 (warm)", + "Pad 3 (polysynth)", + "Pad 4 (choir)", + "Pad 5 (bowed)", + "Pad 6 (metallic)", + "Pad 7 (halo)", + "Pad 8 (sweep)", + // 97-104 Synth Effects + "FX 1 (rain)", + "FX 2 (soundtrack)", + "FX 3 (crystal)", + "FX 4 (atmosphere)", + "FX 5 (brightness)", + "FX 6 (goblins)", + "FX 7 (echoes)", + "FX 8 (sci-fi)", + // 105-112 Ethnic + "Sitar", + "Banjo", + "Shamisen", + "Koto", + "Kalimba", + "Bag pipe", + "Fiddle", + "Shanai", + // 113-120 Percussive + "Tinkle Bell", + "Agogo", + "Steel Drums", + "Woodblock", + "Taiko Drum", + "Melodic Tom", + "Synth Drum", + "Reverse Cymbal", + // 121-128 Sound Effects + "Guitar Fret Noise", + "Breath Noise", + "Seashore", + "Bird Tweet", + "Telephone Ring", + "Helicopter", + "Applause", + "Gunshot" +}; + + +// Notes 25-85 +LPCSTR szMidiPercussionNames[61] = +{ + "Seq Click", + "Brush Tap", + "Brush Swirl", + "Brush Slap", + "Brush Swirl W/Attack", + "Snare Roll", + "Castanet", + "Snare Lo", + "Sticks", + "Bass Drum Lo", + "Open Rim Shot", + "Acoustic Bass Drum", + "Bass Drum 1", + "Side Stick", + "Acoustic Snare", + "Hand Clap", + "Electric Snare", + "Low Floor Tom", + "Closed Hi Hat", + "High Floor Tom", + "Pedal Hi-Hat", + "Low Tom", + "Open Hi-Hat", + "Low-Mid Tom", + "Hi Mid Tom", + "Crash Cymbal 1", + "High Tom", + "Ride Cymbal 1", + "Chinese Cymbal", + "Ride Bell", + "Tambourine", + "Splash Cymbal", + "Cowbell", + "Crash Cymbal 2", + "Vibraslap", + "Ride Cymbal 2", + "Hi Bongo", + "Low Bongo", + "Mute Hi Conga", + "Open Hi Conga", + "Low Conga", + "High Timbale", + "Low Timbale", + "High Agogo", + "Low Agogo", + "Cabasa", + "Maracas", + "Short Whistle", + "Long Whistle", + "Short Guiro", + "Long Guiro", + "Claves", + "Hi Wood Block", + "Low Wood Block", + "Mute Cuica", + "Open Cuica", + "Mute Triangle", + "Open Triangle", + "Shaker", + "Jingle Bell", + "Bell Tree", +}; + + +const WORD kMidiChannelPriority[16] = +{ + 0xFFFE, 0xFFFC, 0xFFF8, 0xFFF0, 0xFFE0, 0xFFC0, 0xFF80, 0xFF00, + 0xFE00, 0xFDFF, 0xF800, 0xF000, 0xE000, 0xC000, 0x8000, 0x0000, +}; + + +/////////////////////////////////////////////////////////////////////////// +// Helper functions + +static LONG getmidilong(LPCBYTE &p, LPCBYTE pmax) +//---------------------------------------------------------- +{ + DWORD n; + UINT a; + + a = (p < pmax) ? *(p++) : 0; + n = 0; + while (a&0x80) + { + n = (n<<7)|(a&0x7F); + a = (p < pmax) ? *(p++) : 0; + } + return (n<<7)|(LONG)a; +} + + +// Returns MOD tempo and tick multiplier +static int ConvertMidiTempo(int tempo_us, int *pTickMultiplier) +//------------------------------------------------------------- +{ + int nBestModTempo = 120; + int nBestError = 1000000; // 1s + int nBestMultiplier = 1; + int nSpeed = gnMidiImportSpeed; + for (int nModTempo=110; nModTempo<=240; nModTempo++) + { + int tick_us = (2500000) / nModTempo; + int nFactor = (tick_us+tempo_us/2) / tempo_us; + if (!nFactor) nFactor = 1; + int nError = tick_us - tempo_us * nFactor; + if (nError < 0) nError = -nError; + if (nError < nBestError) + { + nBestError = nError; + nBestModTempo = nModTempo; + nBestMultiplier = nFactor; + } + if ((!nError) || ((nError<=1) && (nFactor==64))) break; + } + *pTickMultiplier = nBestMultiplier * nSpeed; + return nBestModTempo; +} + + +//////////////////////////////////////////////////////////////////////////////// +// Maps a midi instrument - returns the instrument number in the file +UINT CSoundFile::MapMidiInstrument(DWORD dwBankProgram, UINT nChannel, UINT nNote) +//-------------------------------------------------------------------------------- +{ + INSTRUMENTHEADER *penv; + UINT nProgram = dwBankProgram & 0x7F; + UINT nBank = dwBankProgram >> 7; + + nNote &= 0x7F; + if (nNote >= 120) return 0; + for (UINT i=1; i<=m_nInstruments; i++) if (Headers[i]) + { + INSTRUMENTHEADER *p = Headers[i]; + // Drum Kit ? + if (nChannel == MIDI_DRUMCHANNEL) + { + if (nNote == p->nMidiDrumKey) return i; + } else + // Melodic Instrument + { + if (nProgram == p->nMidiProgram) return i; + } + } + if ((m_nInstruments + 1 >= MAX_INSTRUMENTS) || (m_nSamples + 1 >= MAX_SAMPLES)) return 0; + penv = new INSTRUMENTHEADER; + if (!penv) return 0; + memset(penv, 0, sizeof(INSTRUMENTHEADER)); + m_nSamples++; + m_nInstruments++; + Headers[m_nInstruments] = penv; + penv->wMidiBank = nBank; + penv->nMidiProgram = nProgram; + penv->nMidiChannel = nChannel; + if (nChannel == MIDI_DRUMCHANNEL) penv->nMidiDrumKey = nNote; + penv->nGlobalVol = 128; + penv->nFadeOut = 1024; + penv->nPan = 128; + penv->nPPC = 5*12; + penv->nNNA = NNA_NOTEOFF; + penv->nDCT = (nChannel == MIDI_DRUMCHANNEL) ? DCT_SAMPLE : DCT_NOTE; + penv->nDNA = DNA_NOTEFADE; + for (UINT j=0; j<120; j++) + { + int mapnote = j+1; + if (nChannel == MIDI_DRUMCHANNEL) + { + mapnote = 61; + /*mapnote = 61 + j - nNote; + if (mapnote < 1) mapnote = 1; + if (mapnote > 120) mapnote = 120;*/ + } + penv->Keyboard[j] = m_nSamples; + penv->NoteMap[j] = (BYTE)mapnote; + } + penv->dwFlags |= ENV_VOLUME; + if (nChannel != MIDI_DRUMCHANNEL) penv->dwFlags |= ENV_VOLSUSTAIN; + penv->VolEnv.nNodes=4; + penv->VolEnv.Ticks[0]=0; + penv->VolEnv.Values[0] = 64; + penv->VolEnv.Ticks[1] = 10; + penv->VolEnv.Values[1] = 64; + penv->VolEnv.Ticks[2] = 15; + penv->VolEnv.Values[2] = 48; + penv->VolEnv.Ticks[3] = 20; + penv->VolEnv.Values[3] = 0; + penv->VolEnv.nSustainStart=1; + penv->VolEnv.nSustainEnd=1; + // Sample + Ins[m_nSamples].nPan = 128; + Ins[m_nSamples].nVolume = 256; + Ins[m_nSamples].nGlobalVol = 64; + if (nChannel != MIDI_DRUMCHANNEL) + { + // GM Midi Name + strcpy((char*)penv->name, (char*)szMidiProgramNames[nProgram]); + strcpy((char*)m_szNames[m_nSamples], (char*)szMidiProgramNames[nProgram]); + } else + { + strcpy((char*)penv->name, "Percussions"); + if ((nNote >= 24) && (nNote <= 84)) + strcpy((char*)m_szNames[m_nSamples], (char*)szMidiPercussionNames[nNote-24]); + else + strcpy((char*)m_szNames[m_nSamples], "Percussions"); + } + return m_nInstruments; +} + + +///////////////////////////////////////////////////////////////// +// Loader Status +#define MIDIGLOBAL_SONGENDED 0x0001 +#define MIDIGLOBAL_FROZEN 0x0002 +#define MIDIGLOBAL_UPDATETEMPO 0x0004 +#define MIDIGLOBAL_UPDATEMASTERVOL 0x0008 +// Midi Globals +#define MIDIGLOBAL_GMSYSTEMON 0x0100 +#define MIDIGLOBAL_XGSYSTEMON 0x0200 + + +BOOL CSoundFile::ReadMID(const BYTE *lpStream, DWORD dwMemLength) +//--------------------------------------------------------------- +{ + const MIDIFILEHEADER *pmfh = (const MIDIFILEHEADER *)lpStream; + const MIDITRACKHEADER *pmth; + MODCHANNELSTATE chnstate[MAX_BASECHANNELS]; + MIDICHANNELSTATE midichstate[16]; + MIDITRACK miditracks[MIDI_MAXTRACKS]; + DWORD dwMemPos, dwGlobalFlags, tracks, tempo; + UINT row, pat, midimastervol; + short int division; + int midi_clock, nTempoUsec, nPPQN, nTickMultiplier; + + // Fix import parameters + if (gnMidiImportSpeed < 2) gnMidiImportSpeed = 2; + if (gnMidiImportSpeed > 6) gnMidiImportSpeed = 6; + if (gnMidiPatternLen < 64) gnMidiPatternLen = 64; + if (gnMidiPatternLen > 256) gnMidiPatternLen = 256; + // Detect RMI files + if ((dwMemLength > 12) + && (memcmp(lpStream, "RIFF",4) == 0) + && (memcmp(lpStream, "RMID",4) == 0)) + { + lpStream += 12; + dwMemLength -= 12; + while (dwMemLength > 8) + { + char *id = (char*)lpStream; + DWORD len = *(DWORD *)(lpStream+4); + lpStream += 8; + dwMemLength -= 8; + if ((memcmp(id, "data",4) == 0) && (len < dwMemLength)) + { + dwMemLength = len; + pmfh = (const MIDIFILEHEADER *)lpStream; + break; + } + if (len >= dwMemLength) return FALSE; + lpStream += len; + dwMemLength -= len; + } + } + // MIDI File Header + if ((dwMemLength < sizeof(MIDIFILEHEADER)+8) || (memcmp(pmfh->id, "MThd",4) != 0)) return FALSE; + dwMemPos = 8 + bswapBE32(pmfh->len); + if (dwMemPos >= dwMemLength - 8) return FALSE; + pmth = (MIDITRACKHEADER *)(lpStream+dwMemPos); + tracks = bswapBE16(pmfh->wTrks); + if ((!tracks) || (memcmp(pmth->id, "MTrk", 4) != 0)) return FALSE; + if (tracks > MIDI_MAXTRACKS) tracks = MIDI_MAXTRACKS; + // Reading File... + m_nType = MOD_TYPE_MID; + m_nChannels = 32; + m_nSamples = 0; + m_nInstruments = 0; + m_dwSongFlags |= (SONG_LINEARSLIDES | SONG_INSTRUMENTMODE); + m_szNames[0][0] = 0; + // MIDI->MOD Tempo Conversion + division = bswapBE16(pmfh->wDivision); + if (division < 0) + { + int nFrames = -(division>>8); + int nSubFrames = (division & 0xff); + nPPQN = nFrames * nSubFrames / 2; + if (!nPPQN) nPPQN = 1; + } else + { + nPPQN = (division) ? division : 96; + } + nTempoUsec = 500000 / nPPQN; + tempo = ConvertMidiTempo(nTempoUsec, &nTickMultiplier); + m_nDefaultTempo = tempo; + m_nDefaultSpeed = gnMidiImportSpeed; + m_nDefaultGlobalVolume = 256; + midimastervol = m_nDefaultGlobalVolume; + + // Initializing + memset(Order, 0xFF, sizeof(Order)); + memset(chnstate, 0, sizeof(chnstate)); + memset(miditracks, 0, sizeof(miditracks)); + memset(midichstate, 0, sizeof(midichstate)); + // Initializing Patterns + Order[0] = 0; + for (UINT ipat=0; ipat<MAX_PATTERNS; ipat++) PatternSize[ipat] = gnMidiPatternLen; + // Initializing Channels + for (UINT ics=0; ics<MAX_BASECHANNELS; ics++) + { + // Channel settings + ChnSettings[ics].nPan = 128; + ChnSettings[ics].nVolume = 64; + ChnSettings[ics].dwFlags = 0; + // Channels state + chnstate[ics].pan = 128; + chnstate[ics].pitchsrc = 0x2000; + chnstate[ics].pitchdest = 0x2000; + } + // Initializing Track Pointers + for (UINT itrk=0; itrk<tracks; itrk++) + { + miditracks[itrk].nexteventtime = -1; + miditracks[itrk].status = 0x2F; + pmth = (MIDITRACKHEADER *)(lpStream+dwMemPos); + if (dwMemPos + 8 >= dwMemLength) break; + DWORD len = bswapBE32(pmth->len); + if ((memcmp(pmth->id, "MTrk", 4) == 0) && (dwMemPos + 8 + len <= dwMemLength)) + { + // Initializing midi tracks + miditracks[itrk].ptracks = lpStream+dwMemPos+8; + miditracks[itrk].ptrmax = miditracks[itrk].ptracks + len; + miditracks[itrk].nexteventtime = getmidilong(miditracks[itrk].ptracks, miditracks[itrk].ptrmax); + } + dwMemPos += 8 + len; + } + // Initializing midi channels state + for (UINT imidi=0; imidi<16; imidi++) + { + midichstate[imidi].pan = 128; // middle + midichstate[imidi].expression = 128; // no attenuation + midichstate[imidi].volume = 80; // GM specs defaults to 100 + midichstate[imidi].pitchbend = 0x2000; // Pitch Bend Amount + midichstate[imidi].pitchbendrange = 64; // Pitch Bend Range: +/- 2 semitones + } + //////////////////////////////////////////////////////////////////////////// + // Main Midi Sequencer Loop + pat = 0; + row = 0; + midi_clock = 0; + dwGlobalFlags = MIDIGLOBAL_UPDATETEMPO | MIDIGLOBAL_FROZEN; + do + { + // Allocate current pattern if not allocated yet + if (!Patterns[pat]) + { + Patterns[pat] = AllocatePattern(PatternSize[pat], m_nChannels); + if (!Patterns[pat]) break; + } + dwGlobalFlags |= MIDIGLOBAL_SONGENDED; + MODCOMMAND *m = Patterns[pat] + row * m_nChannels; + // Parse Tracks + for (UINT trk=0; trk<tracks; trk++) if (miditracks[trk].ptracks) + { + MIDITRACK *ptrk = &miditracks[trk]; + dwGlobalFlags &= ~MIDIGLOBAL_SONGENDED; + while ((ptrk->ptracks) && (ptrk->nexteventtime >= 0) && (midi_clock+(nTickMultiplier>>2) >= ptrk->nexteventtime)) + { + if (ptrk->ptracks[0] & 0x80) ptrk->status = *(ptrk->ptracks++); + switch(ptrk->status) + { + ///////////////////////////////////////////////////////////////////// + // End Of Track + case 0x2F: + // End Of Song + case 0xFC: + ptrk->ptracks = NULL; + break; + + ///////////////////////////////////////////////////////////////////// + // SYSEX messages + case 0xF0: + case 0xF7: + { + LONG len = getmidilong(ptrk->ptracks, ptrk->ptrmax); + if ((len > 1) && (ptrk->ptracks + len <ptrk->ptrmax) && (ptrk->ptracks[len-1] == 0xF7)) + { + DWORD dwSysEx1 = 0, dwSysEx2 = 0; + if (len >= 4) dwSysEx1 = (*((DWORD *)(ptrk->ptracks))) & 0x7F7F7F7F; + if (len >= 8) dwSysEx2 = (*((DWORD *)(ptrk->ptracks+4))) & 0x7F7F7F7F; + // GM System On + if ((len == 5) && (dwSysEx1 == 0x01097F7E)) + { + dwGlobalFlags |= MIDIGLOBAL_GMSYSTEMON; + } else + // XG System On + if ((len == 8) && ((dwSysEx1 & 0xFFFFF0FF) == 0x004c1043) && (dwSysEx2 == 0x77007e00)) + { + dwGlobalFlags |= MIDIGLOBAL_XGSYSTEMON; + } else + // Midi Master Volume + if ((len == 7) && (dwSysEx1 == 0x01047F7F)) + { + midimastervol = midivolumetolinear(ptrk->ptracks[5] & 0x7F) >> 8; + if (midimastervol < 16) midimastervol = 16; + dwGlobalFlags |= MIDIGLOBAL_UPDATEMASTERVOL; + } + } + ptrk->ptracks += len; + } + break; + + ////////////////////////////////////////////////////////////////////// + // META-events: FF.code.len.data[len] + case 0xFF: + { + UINT i = *(ptrk->ptracks++); + LONG len = getmidilong(ptrk->ptracks, ptrk->ptrmax); + if (ptrk->ptracks+len > ptrk->ptrmax) + { + // EOF + ptrk->ptracks = NULL; + } else + switch(i) + { + // FF.01 [text]: Song Information + case 0x01: + if (!len) break; + if ((len < 32) && (!m_szNames[0][0])) + { + memcpy(m_szNames[0], ptrk->ptracks, len); + m_szNames[0][len] = 0; + } else + if ((!m_lpszSongComments) && (ptrk->ptracks[0]) && (ptrk->ptracks[0] < 0x7F)) + { + m_lpszSongComments = new char [len+1]; + if (m_lpszSongComments) + { + memcpy(m_lpszSongComments, ptrk->ptracks, len); + m_lpszSongComments[len] = 0; + } + } + break; + // FF.02 [text]: Song Copyright + case 0x02: + if (!len) break; + if ((!m_lpszSongComments) && (ptrk->ptracks[0]) && (ptrk->ptracks[0] < 0x7F) && (len > 7)) + { + m_lpszSongComments = new char [len+1]; + if (m_lpszSongComments) + { + memcpy(m_lpszSongComments, ptrk->ptracks, len); + m_lpszSongComments[len] = 0; + } + } + break; + // FF.03: Sequence Name + case 0x03: + // FF.06: Sequence Text (->Pattern names) + case 0x06: + if ((len > 1) && (!trk)) + { + UINT k = (len < 32) ? len : 31; + CHAR s[32]; + memcpy(s, ptrk->ptracks, k); + s[k] = 0; + if ((!strnicmp((char*)s, "Copyri", 6)) || (!s[0])) break; + if (i == 0x03) + { + if (!m_szNames[0][0]) strcpy((char*)m_szNames[0], (char*)s); + } + } + break; + // FF.07: Cue Point (marker) + // FF.20: Channel Prefix + // FF.2F: End of Track + case 0x2F: + ptrk->status = 0x2F; + ptrk->ptracks = NULL; + break; + // FF.51 [tttttt]: Set Tempo + case 0x51: + { + LONG l = ptrk->ptracks[0]; + l = (l << 8) | ptrk->ptracks[1]; + l = (l << 8) | ptrk->ptracks[2]; + if (l <= 0) break; + nTempoUsec = l / nPPQN; + if (nTempoUsec < 100) nTempoUsec = 100; + tempo = ConvertMidiTempo(nTempoUsec, &nTickMultiplier); + dwGlobalFlags |= MIDIGLOBAL_UPDATETEMPO; + } + break; + // FF.58: Time Signature + // FF.7F: Sequencer-Specific + } + if (ptrk->ptracks) ptrk->ptracks += len; + } + break; + + ////////////////////////////////////////////////////////////////////////// + // Regular Voice Events + default: + { + UINT midich = (ptrk->status & 0x0F)+1; + UINT midist = ptrk->status & 0xF0; + MIDICHANNELSTATE *pmidich = &midichstate[midich-1]; + UINT note, velocity; + + switch(midist) + { + ////////////////////////////////// + // Note Off: 80.note.velocity + case 0x80: + // Note On: 90.note.velocity + case 0x90: + note = ptrk->ptracks[0] & 0x7F; + velocity = (midist == 0x90) ? (ptrk->ptracks[1] & 0x7F) : 0; + ptrk->ptracks += 2; + // Note On: 90.note.velocity + if (velocity) + { + // Start counting rows + dwGlobalFlags &= ~MIDIGLOBAL_FROZEN; + // if the note is already playing, we reuse this channel + UINT nchn = pmidich->note_on[note]; + if ((nchn) && (chnstate[nchn-1].parent != midich)) nchn = 0; + // or else, we look for an available child channel + if (!nchn) + { + for (UINT i=0; i<m_nChannels; i++) if (chnstate[i].parent == midich) + { + if ((!chnstate[i].note) && ((!m[i].note) || (m[i].note & 0x80))) + { + // found an available channel + nchn = i+1; + break; + } + } + } + // still nothing? in this case, we try to allocate a new mod channel + if (!nchn) + { + for (UINT i=0; i<m_nChannels; i++) if (!chnstate[i].parent) + { + nchn = i+1; + chnstate[i].parent = midich; + break; + } + } + // still not? we have to steal a voice from another channel + // We found our channel: let's do the note on + if (nchn) + { + pmidich->note_on[note] = nchn; + nchn--; + chnstate[nchn].pitchsrc = pmidich->pitchbend; + chnstate[nchn].pitchdest = pmidich->pitchbend; + chnstate[nchn].flags &= ~CHNSTATE_NOTEOFFPENDING; + chnstate[nchn].idlecount = 0; + chnstate[nchn].note = note+1; + int realnote = note; + if (midich != 10) + { + realnote += (((int)pmidich->pitchbend - 0x2000) * pmidich->pitchbendrange) / (0x2000*32); + if (realnote < 0) realnote = 0; + if (realnote > 119) realnote = 119; + } + m[nchn].note = realnote+1; + m[nchn].instr = MapMidiInstrument(pmidich->program + ((UINT)pmidich->bank << 7), midich, note); + m[nchn].volcmd = VOLCMD_VOLUME; + LONG vol = midivolumetolinear(velocity) >> 8; + vol = (vol * (LONG)pmidich->volume * (LONG)pmidich->expression) >> 13; + if (vol > 256) vol = 256; + if (vol < 4) vol = 4; + m[nchn].vol = (BYTE)(vol>>2); + // Channel Panning + if ((!m[nchn].command) && (pmidich->pan != chnstate[nchn].pan)) + { + chnstate[nchn].pan = pmidich->pan; + m[nchn].param = pmidich->pan; + m[nchn].command = CMD_PANNING8; + } + } + } else + // Note Off; 90.note.00 + if (!(dwGlobalFlags & MIDIGLOBAL_FROZEN)) + { + UINT nchn = pmidich->note_on[note]; + if (nchn) + { + nchn--; + chnstate[nchn].flags |= CHNSTATE_NOTEOFFPENDING; + chnstate[nchn].note = 0; + pmidich->note_on[note] = 0; + } else + { + for (UINT i=0; i<m_nChannels; i++) + { + if ((chnstate[i].parent == midich) && (chnstate[i].note == note+1)) + { + chnstate[i].note = 0; + chnstate[i].flags |= CHNSTATE_NOTEOFFPENDING; + } + } + } + } + break; + + /////////////////////////////////// + // A0.xx.yy: Aftertouch + case 0xA0: + { + ptrk->ptracks += 2; + } + break; + + /////////////////////////////////// + // B0: Control Change + case 0xB0: + { + UINT controller = ptrk->ptracks[0]; + UINT value = ptrk->ptracks[1] & 0x7F; + ptrk->ptracks += 2; + switch(controller) + { + // Bn.00.xx: Bank Select MSB (GS) + case 0x00: + pmidich->bank &= 0x7F; + pmidich->bank |= (value << 7); + break; + // Bn.01.xx: Modulation Depth + case 0x01: + pmidich->pitchbendrange = value; + break; + // Bn.07.xx: Volume + case 0x07: + pmidich->volume = (BYTE)(midivolumetolinear(value) >> 9); + break; + // Bn.0B.xx: Expression + case 0x0B: + pmidich->expression = (BYTE)(midivolumetolinear(value) >> 9); + break; + // Bn.0A.xx: Pan + case 0x0A: + pmidich->pan = value * 2; + break; + // Bn.20.xx: Bank Select LSB (GS) + case 0x20: + pmidich->bank &= (0x7F << 7); + pmidich->bank |= value; + break; + // Bn.79.00: Reset All Controllers (GM) + case 0x79: + pmidich->modulation = 0; + pmidich->expression = 128; + pmidich->pitchbend = 0x2000; + pmidich->pitchbendrange = 64; + // Should also reset pedals (40h-43h), NRP, RPN, aftertouch + break; + // Bn.78.00: All Sound Off (GS) + // Bn.7B.00: All Notes Off (GM) + case 0x78: + case 0x7B: + if (value == 0x00) + { + // All Notes Off + for (UINT k=0; k<m_nChannels; k++) + { + if (chnstate[k].note) + { + chnstate[k].flags |= CHNSTATE_NOTEOFFPENDING; + chnstate[k].note = 0; + } + } + } + break; + //////////////////////////////////// + // Controller List + // + // Bn.02.xx: Breath Control + // Bn.04.xx: Foot Pedal + // Bn.05.xx: Portamento Time (Glissando Time) + // Bn.06.xx: Data Entry MSB + // Bn.08.xx: Balance + // Bn.10-13.xx: GP Control #1-#4 + // Bn.20-3F.xx: Data LSB for controllers 0-31 + // Bn.26.xx: Data Entry LSB + // Bn.40.xx: Hold Pedal #1 + // Bn.41.xx: Portamento (GS) + // Bn.42.xx: Sostenuto (GS) + // Bn.43.xx: Soft Pedal (GS) + // Bn.44.xx: Legato Pedal + // Bn.45.xx: Hold Pedal #2 + // Bn.46.xx: Sound Variation + // Bn.47.xx: Sound Timbre + // Bn.48.xx: Sound Release Time + // Bn.49.xx: Sound Attack Time + // Bn.4A.xx: Sound Brightness + // Bn.4B-4F.xx: Sound Control #6-#10 + // Bn.50-53.xx: GP Control #5-#8 + // Bn.54.xx: Portamento Control (GS) + // Bn.5B.xx: Reverb Level (GS) + // Bn.5C.xx: Tremolo Depth + // Bn.5D.xx: Chorus Level (GS) + // Bn.5E.xx: Celeste Depth + // Bn.5F.xx: Phaser Depth + // Bn.60.xx: Data Increment + // Bn.61.xx: Data Decrement + // Bn.62.xx: Non-RPN Parameter LSB (GS) + // Bn.63.xx: Non-RPN Parameter MSB (GS) + // Bn.64.xx: RPN Parameter LSB (GM) + // Bn.65.xx: RPN Parameter MSB (GM) + // Bn.7A.00: Local On/Off + // Bn.7C.00: Omni Mode Off + // Bn.7D.00: Omni Mode On + // Bn.7E.mm: Mono Mode On + // Bn.7F.00: Poly Mode On + } + } + break; + + //////////////////////////////// + // C0.pp: Program Change + case 0xC0: + { + pmidich->program = ptrk->ptracks[0] & 0x7F; + ptrk->ptracks++; + } + break; + + //////////////////////////////// + // D0: Channel Aftertouch (Polyphonic Key Pressure) + case 0xD0: + { + ptrk->ptracks++; + } + break; + + //////////////////////////////// + // E0: Pitch Bend + case 0xE0: + { + pmidich->pitchbend = (WORD)(((UINT)ptrk->ptracks[1] << 7) + (ptrk->ptracks[0] & 0x7F)); + for (UINT i=0; i<128; i++) if (pmidich->note_on[i]) + { + UINT nchn = pmidich->note_on[i]-1; + if (chnstate[nchn].parent == midich) + { + chnstate[nchn].pitchdest = pmidich->pitchbend; + } + } + ptrk->ptracks+=2; + } + break; + + ////////////////////////////////////// + // F0 & Unsupported commands: skip it + default: + ptrk->ptracks++; + } + }} // switch+default + // Process to next event + if (ptrk->ptracks) + { + ptrk->nexteventtime += getmidilong(ptrk->ptracks, ptrk->ptrmax); + } + if (ptrk->ptracks >= ptrk->ptrmax) ptrk->ptracks = NULL; + } + // End reached? + if (ptrk->ptracks >= ptrk->ptrmax) ptrk->ptracks = NULL; + } + + //////////////////////////////////////////////////////////////////// + // Move to next row + if (!(dwGlobalFlags & MIDIGLOBAL_FROZEN)) + { + // Check MOD channels status + for (UINT ichn=0; ichn<m_nChannels; ichn++) + { + // Pending Global Effects ? + if (!m[ichn].command) + { + if ((chnstate[ichn].pitchsrc != chnstate[ichn].pitchdest) && (chnstate[ichn].parent)) + { + int newpitch = chnstate[ichn].pitchdest; + int pitchbendrange = midichstate[chnstate[ichn].parent-1].pitchbendrange; + // +/- 256 for +/- pitch bend range + int slideamount = (newpitch - (int)chnstate[ichn].pitchsrc) / (int)32; + if (slideamount) + { + const int ppdiv = (16 * 128 * (gnMidiImportSpeed-1)); + newpitch = (int)chnstate[ichn].pitchsrc + slideamount; + if (slideamount < 0) + { + int param = (-slideamount * pitchbendrange + ppdiv/2) / ppdiv; + if (param >= 0x80) param = 0x80; + if (param > 0) + { + m[ichn].param = (BYTE)param; + m[ichn].command = CMD_PORTAMENTODOWN; + } + } else + { + int param = (slideamount * pitchbendrange + ppdiv/2) / ppdiv; + if (param >= 0x80) param = 0x80; + if (param > 0) + { + m[ichn].param = (BYTE)param; + m[ichn].command = CMD_PORTAMENTOUP; + } + } + } + chnstate[ichn].pitchsrc = (WORD)newpitch; + + } else + if (dwGlobalFlags & MIDIGLOBAL_UPDATETEMPO) + { + m[ichn].command = CMD_TEMPO; + m[ichn].param = (BYTE)tempo; + dwGlobalFlags &= ~MIDIGLOBAL_UPDATETEMPO; + } else + if (dwGlobalFlags & MIDIGLOBAL_UPDATEMASTERVOL) + { + m[ichn].command = CMD_GLOBALVOLUME; + m[ichn].param = midimastervol >> 1; // 0-128 + dwGlobalFlags &= ~MIDIGLOBAL_UPDATEMASTERVOL; + } + } + // Check pending noteoff events for m[ichn] + if (!m[ichn].note) + { + if (chnstate[ichn].flags & CHNSTATE_NOTEOFFPENDING) + { + chnstate[ichn].flags &= ~CHNSTATE_NOTEOFFPENDING; + m[ichn].note = 0xFF; + } + // Check State of channel + chnstate[ichn].idlecount++; + if ((chnstate[ichn].note) && (chnstate[ichn].idlecount >= 50)) + { + chnstate[ichn].note = 0; + m[ichn].note = 0xFF; // only if not drum channel ? + } else + if (chnstate[ichn].idlecount >= 500) // 20secs of inactivity + { + chnstate[ichn].idlecount = 0; + chnstate[ichn].parent = 0; + } + } + } + + if ((++row) >= PatternSize[pat]) + { + pat++; + if (pat >= MAX_PATTERNS-1) break; + Order[pat] = pat; + Order[pat+1] = 0xFF; + row = 0; + } + } + + // Increase midi clock + midi_clock += nTickMultiplier; + } while (!(dwGlobalFlags & MIDIGLOBAL_SONGENDED)); + return TRUE; +} + +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/modplug/snd_eq.cxx Fri Dec 07 21:35:28 2007 +0300 @@ -0,0 +1,228 @@ +/* + * This program is free software; you can redistribute it and modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the license or (at your + * option) any later version. + * + * Authors: Olivier Lapicque <olivierl@jps.net> + * + * Name Date Description + * + * Olivier Lapicque --/--/-- Creation + * Trevor Nunes 26/01/04 conditional compilation for AMD,MMX calls + * +*/ +#include "stdafx.h" +#include "sndfile.h" +#include <math.h> + + +#define EQ_BANDWIDTH 2.0 +#define EQ_ZERO 0.000001 +#define REAL float + +extern REAL MixFloatBuffer[]; + +extern void StereoMixToFloat(const int *pSrc, float *pOut1, float *pOut2, UINT nCount); +extern void FloatToStereoMix(const float *pIn1, const float *pIn2, int *pOut, UINT nCount); +extern void MonoMixToFloat(const int *pSrc, float *pOut, UINT nCount); +extern void FloatToMonoMix(const float *pIn, int *pOut, UINT nCount); + +typedef struct _EQBANDSTRUCT +{ + REAL a0, a1, a2, b1, b2; + REAL x1, x2, y1, y2; + REAL Gain, CenterFrequency; + BOOL bEnable; +} EQBANDSTRUCT, *PEQBANDSTRUCT; + +UINT gEqLinearToDB[33] = +{ + 16, 19, 22, 25, 28, 31, 34, 37, + 40, 43, 46, 49, 52, 55, 58, 61, + 64, 76, 88, 100, 112, 124, 136, 148, + 160, 172, 184, 196, 208, 220, 232, 244, 256 +}; + + +//static REAL f2ic = (REAL)(1 << 28); +//static REAL i2fc = (REAL)(1.0 / (1 << 28)); + +static EQBANDSTRUCT gEQ[MAX_EQ_BANDS*2] = +{ + // Default: Flat EQ + {0,0,0,0,0, 0,0,0,0, 1, 120, FALSE}, + {0,0,0,0,0, 0,0,0,0, 1, 600, FALSE}, + {0,0,0,0,0, 0,0,0,0, 1, 1200, FALSE}, + {0,0,0,0,0, 0,0,0,0, 1, 3000, FALSE}, + {0,0,0,0,0, 0,0,0,0, 1, 6000, FALSE}, + {0,0,0,0,0, 0,0,0,0, 1, 10000, FALSE}, + {0,0,0,0,0, 0,0,0,0, 1, 120, FALSE}, + {0,0,0,0,0, 0,0,0,0, 1, 600, FALSE}, + {0,0,0,0,0, 0,0,0,0, 1, 1200, FALSE}, + {0,0,0,0,0, 0,0,0,0, 1, 3000, FALSE}, + {0,0,0,0,0, 0,0,0,0, 1, 6000, FALSE}, + {0,0,0,0,0, 0,0,0,0, 1, 10000, FALSE}, +}; + +void EQFilter(EQBANDSTRUCT *pbs, REAL *pbuffer, UINT nCount) +//---------------------------------------------------------- +{ + for (UINT i=0; i<nCount; i++) + { + REAL x = pbuffer[i]; + REAL y = pbs->a1 * pbs->x1 + pbs->a2 * pbs->x2 + pbs->a0 * x + pbs->b1 * pbs->y1 + pbs->b2 * pbs->y2; + pbs->x2 = pbs->x1; + pbs->y2 = pbs->y1; + pbs->x1 = x; + pbuffer[i] = y; + pbs->y1 = y; + } +} + +void CSoundFile::EQMono(int *pbuffer, UINT nCount) +//------------------------------------------------ +{ + MonoMixToFloat(pbuffer, MixFloatBuffer, nCount); + for (UINT b=0; b<MAX_EQ_BANDS; b++) + { + if ((gEQ[b].bEnable) && (gEQ[b].Gain != 1.0f)) + EQFilter(&gEQ[b], MixFloatBuffer, nCount); + } + FloatToMonoMix(MixFloatBuffer, pbuffer, nCount); +} + +void CSoundFile::EQStereo(int *pbuffer, UINT nCount) +//-------------------------------------------------- +{ + StereoMixToFloat(pbuffer, MixFloatBuffer, MixFloatBuffer+MIXBUFFERSIZE, nCount); + + for (UINT bl=0; bl<MAX_EQ_BANDS; bl++) + { + if ((gEQ[bl].bEnable) && (gEQ[bl].Gain != 1.0f)) + EQFilter(&gEQ[bl], MixFloatBuffer, nCount); + } + for (UINT br=MAX_EQ_BANDS; br<MAX_EQ_BANDS*2; br++) + { + if ((gEQ[br].bEnable) && (gEQ[br].Gain != 1.0f)) + EQFilter(&gEQ[br], MixFloatBuffer+MIXBUFFERSIZE, nCount); + } + + FloatToStereoMix(MixFloatBuffer, MixFloatBuffer+MIXBUFFERSIZE, pbuffer, nCount); + +} + +void CSoundFile::InitializeEQ(BOOL bReset) +//---------------------------------------- +{ + REAL fMixingFreq = (REAL)gdwMixingFreq; + // Gain = 0.5 (-6dB) .. 2 (+6dB) + for (UINT band=0; band<MAX_EQ_BANDS*2; band++) if (gEQ[band].bEnable) + { + REAL k, k2, r, f; + REAL v0, v1; + BOOL b = bReset; + + f = gEQ[band].CenterFrequency / fMixingFreq; + if (f > 0.45f) gEQ[band].Gain = 1; + // if (f > 0.25) f = 0.25; + // k = tan(PI*f); + k = f * 3.141592654f; + k = k + k*f; +// if (k > (REAL)0.707) k = (REAL)0.707; + k2 = k*k; + v0 = gEQ[band].Gain; + v1 = 1; + if (gEQ[band].Gain < 1.0) + { + v0 *= (0.5f/EQ_BANDWIDTH); + v1 *= (0.5f/EQ_BANDWIDTH); + } else + { + v0 *= (1.0f/EQ_BANDWIDTH); + v1 *= (1.0f/EQ_BANDWIDTH); + } + r = (1 + v0*k + k2) / (1 + v1*k + k2); + if (r != gEQ[band].a0) + { + gEQ[band].a0 = r; + b = TRUE; + } + r = 2 * (k2 - 1) / (1 + v1*k + k2); + if (r != gEQ[band].a1) + { + gEQ[band].a1 = r; + b = TRUE; + } + r = (1 - v0*k + k2) / (1 + v1*k + k2); + if (r != gEQ[band].a2) + { + gEQ[band].a2 = r; + b = TRUE; + } + r = - 2 * (k2 - 1) / (1 + v1*k + k2); + if (r != gEQ[band].b1) + { + gEQ[band].b1 = r; + b = TRUE; + } + r = - (1 - v1*k + k2) / (1 + v1*k + k2); + if (r != gEQ[band].b2) + { + gEQ[band].b2 = r; + b = TRUE; + } + if (b) + { + gEQ[band].x1 = 0; + gEQ[band].x2 = 0; + gEQ[band].y1 = 0; + gEQ[band].y2 = 0; + } + } else + { + gEQ[band].a0 = 0; + gEQ[band].a1 = 0; + gEQ[band].a2 = 0; + gEQ[band].b1 = 0; + gEQ[band].b2 = 0; + gEQ[band].x1 = 0; + gEQ[band].x2 = 0; + gEQ[band].y1 = 0; + gEQ[band].y2 = 0; + } +} + + +void CSoundFile::SetEQGains(const UINT *pGains, UINT nGains, const UINT *pFreqs, BOOL bReset) +//------------------------------------------------------------------------------------------- +{ + for (UINT i=0; i<MAX_EQ_BANDS; i++) + { + REAL g, f = 0; + if (i < nGains) + { + UINT n = pGains[i]; +// if (n > 32) n = 32; + g = 1.0 + (((double)n) / 64.0); + if (pFreqs) f = (REAL)(int)pFreqs[i]; + } else + { + g = 1; + } + gEQ[i].Gain = g; + gEQ[i].CenterFrequency = f; + gEQ[i+MAX_EQ_BANDS].Gain = g; + gEQ[i+MAX_EQ_BANDS].CenterFrequency = f; + if (f > 20.0f && i < nGains) /* don't enable bands outside... */ + { + gEQ[i].bEnable = TRUE; + gEQ[i+MAX_EQ_BANDS].bEnable = TRUE; + } else + { + gEQ[i].bEnable = FALSE; + gEQ[i+MAX_EQ_BANDS].bEnable = FALSE; + } + } + InitializeEQ(bReset); +}