Added libOPNMIDI sources

This commit is contained in:
Vitaly Novichkov 2018-03-24 17:52:52 +03:00
parent 3072c9bf7c
commit 46942cb27f
15 changed files with 8776 additions and 0 deletions

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,38 @@
// YM2612 FM sound chip emulator interface
// Game_Music_Emu 0.6.0
#ifndef YM2612_EMU_H
#define YM2612_EMU_H
struct OPNMIDI_Ym2612_Impl;
class OPNMIDI_Ym2612_Emu {
OPNMIDI_Ym2612_Impl* impl;
public:
OPNMIDI_Ym2612_Emu() { impl = 0; }
~OPNMIDI_Ym2612_Emu();
// Set output sample rate and chip clock rates, in Hz. Returns non-zero
// if error.
const char* set_rate( double sample_rate, double clock_rate );
// Reset to power-up state
void reset();
// Mute voice n if bit n (1 << n) of mask is set
enum { channel_count = 6 };
void mute_voices( int mask );
// Write addr to register 0 then data to register 1
void write0( int addr, int data );
// Write addr to register 2 then data to register 3
void write1( int addr, int data );
// Run and add pair_count samples into current output buffer contents
typedef short sample_t;
enum { out_chan_count = 2 }; // stereo
void run( int pair_count, sample_t* out );
};
#endif

View file

@ -0,0 +1,206 @@
#ifndef bqw_fraction_h
#define bqw_fraction_h
#include <cmath>
#include <limits>
/* Fraction number handling.
* Copyright (C) 1992,2001 Bisqwit (http://iki.fi/bisqwit/)
*/
template<typename inttype=int>
class fraction
{
inttype num1, num2;
typedef fraction<inttype> self;
void Optim();
#if 1
inline void Debug(char, const self &) { }
#else
inline void Debug(char op, const self &b)
{
cerr << nom() << '/' << denom() << ' ' << op
<< ' ' << b.nom() << '/' << denom()
<< ":\n";
}
#endif
public:
void set(inttype n, inttype d) { num1=n; num2=d; Optim(); }
fraction() : num1(0), num2(1) { }
fraction(inttype value) : num1(value), num2(1) { }
fraction(inttype n, inttype d) : num1(n), num2(d) { }
fraction(int value) : num1(value), num2(1) { }
template<typename floattype>
explicit fraction(const floattype value) { operator= (value); }
inline double value() const {return nom() / (double)denom(); }
inline long double valuel() const {return nom() / (long double)denom(); }
self &operator+= (const inttype &value) { num1+=value*denom(); Optim(); return *this; }
self &operator-= (const inttype &value) { num1-=value*denom(); Optim(); return *this; }
self &operator*= (const inttype &value) { num1*=value; Optim(); return *this; }
self &operator/= (const inttype &value) { num2*=value; Optim(); return *this; }
self &operator+= (const self &b);
self &operator-= (const self &b);
self &operator*= (const self &b) { Debug('*',b);num1*=b.nom(); num2*=b.denom(); Optim(); return *this; }
self &operator/= (const self &b) { Debug('/',b);num1*=b.denom(); num2*=b.nom(); Optim(); return *this; }
self operator- () const { return self(-num1, num2); }
#define fraction_blah_func(op1, op2) \
self operator op1 (const self &b) const { self tmp(*this); tmp op2 b; return tmp; }
fraction_blah_func( +, += )
fraction_blah_func( -, -= )
fraction_blah_func( /, /= )
fraction_blah_func( *, *= )
#undef fraction_blah_func
#define fraction_blah_func(op) \
bool operator op(const self &b) const { return value() op b.value(); } \
bool operator op(inttype b) const { return value() op b; }
fraction_blah_func( < )
fraction_blah_func( > )
fraction_blah_func( <= )
fraction_blah_func( >= )
#undef fraction_blah_func
const inttype &nom() const { return num1; }
const inttype &denom() const { return num2; }
inline bool operator == (inttype b) const { return denom() == 1 && nom() == b; }
inline bool operator != (inttype b) const { return denom() != 1 || nom() != b; }
inline bool operator == (const self &b) const { return denom()==b.denom() && nom()==b.nom(); }
inline bool operator != (const self &b) const { return denom()!=b.denom() || nom()!=b.nom(); }
//operator bool () const { return nom() != 0; }
inline bool negative() const { return (nom() < 0) ^ (denom() < 0); }
self &operator= (const inttype value) { num2=1; num1=value; return *this; }
//self &operator= (int value) { num2=1; num1=value; return *this; }
self &operator= (double orig) { return *this = (long double)orig; }
self &operator= (long double orig);
};
template<typename inttype>
void fraction<inttype>::Optim()
{
/* Euclidean algorithm */
inttype n1, n2, nn1, nn2;
nn1 = std::numeric_limits<inttype>::is_signed ? (num1 >= 0 ? num1 : -num1) : num1;
nn2 = std::numeric_limits<inttype>::is_signed ? (num2 >= 0 ? num2 : -num2) : num2;
if(nn1 < nn2)
n1 = num1, n2 = num2;
else
n1 = num2, n2 = num1;
if(!num1) { num2 = 1; return; }
for(;;)
{
//fprintf(stderr, "%d/%d: n1=%d,n2=%d\n", nom(),denom(),n1,n2);
inttype tmp = n2 % n1;
if(!tmp)break;
n2 = n1;
n1 = tmp;
}
num1 /= n1;
num2 /= n1;
//fprintf(stderr, "result: %d/%d\n\n", nom(), denom());
}
template<typename inttype>
inline const fraction<inttype> abs(const fraction<inttype> &f)
{
return fraction<inttype>(abs(f.nom()), abs(f.denom()));
}
#define fraction_blah_func(op) \
template<typename inttype> \
fraction<inttype> operator op \
(const inttype bla, const fraction<inttype> &b) \
{ return fraction<inttype> (bla) op b; }
fraction_blah_func( + )
fraction_blah_func( - )
fraction_blah_func( * )
fraction_blah_func( / )
#undef fraction_blah_func
#define fraction_blah_func(op1, op2) \
template<typename inttype> \
fraction<inttype> &fraction<inttype>::operator op2 (const fraction<inttype> &b) \
{ \
inttype newnom = nom()*b.denom() op1 denom()*b.nom(); \
num2 *= b.denom(); \
num1 = newnom; \
Optim(); \
return *this; \
}
fraction_blah_func( +, += )
fraction_blah_func( -, -= )
#undef fraction_blah_func
template<typename inttype>
fraction<inttype> &fraction<inttype>::operator= (long double orig)
{
if(orig == 0.0)
{
set(0, 0);
return *this;
}
inttype cf[25];
for(int maxdepth=1; maxdepth<25; ++maxdepth)
{
inttype u,v;
long double virhe, a=orig;
int i, viim;
for(i = 0; i < maxdepth; ++i)
{
cf[i] = (inttype)a;
if(cf[i]-1 > cf[i])break;
a = 1.0 / (a - cf[i]);
}
for(viim=i-1; i < maxdepth; ++i)
cf[i] = 0;
u = cf[viim];
v = 1;
for(i = viim-1; i >= 0; --i)
{
inttype w = cf[i] * u + v;
v = u;
u = w;
}
virhe = (orig - (u / (long double)v)) / orig;
set(u, v);
//if(verbose > 4)
// cerr << "Guess: " << *this << " - error = " << virhe*100 << "%\n";
if(virhe < 1e-8 && virhe > -1e-8)break;
}
//if(verbose > 4)
//{
// cerr << "Fraction=" << orig << ": " << *this << endl;
//}
return *this;
}
/*
template<typename inttype>
ostream &operator << (ostream &dest, const fraction<inttype> &m)
{
if(m.denom() == (inttype)1) return dest << m.nom();
return dest << m.nom() << '/' << m.denom();
}
*/
#endif

View file

@ -0,0 +1,81 @@
/*
* libOPNMIDI is a free MIDI to WAV conversion library with OPN2 (YM2612) emulation
*
* MIDI parser and player (Original code from ADLMIDI): Copyright (c) 2010-2014 Joel Yliluoma <bisqwit@iki.fi>
* ADLMIDI Library API: Copyright (c) 2016 Vitaly Novichkov <admin@wohlnet.ru>
*
* Library is based on the ADLMIDI, a MIDI player for Linux and Windows with OPL3 emulation:
* http://iki.fi/bisqwit/source/adlmidi.html
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <stdint.h>
#ifdef ADLMIDI_buildAsApp
#include <SDL2/SDL.h>
class MutexType
{
SDL_mutex* mut;
public:
MutexType() : mut(SDL_CreateMutex()) { }
~MutexType() { SDL_DestroyMutex(mut); }
void Lock() { SDL_mutexP(mut); }
void Unlock() { SDL_mutexV(mut); }
};
#endif
/* *********** FM Operator indexes *********** */
#define OPERATOR1 0
#define OPERATOR2 1
#define OPERATOR3 2
#define OPERATOR4 3
/* *********** FM Operator indexes *end******* */
struct OPN_Operator
{
//! Raw register data
uint8_t data[7];
/*
Bytes:
0 - Deture/Multiple
1 - Total Level
2 - Rate Scale / Attack
3 - Amplitude modulation / Decay-1
4 - Decay-2
5 - Systain / Release
6 - SSG-EG byte
*/
};
struct opnInstData
{
//! Operators prepared for sending to OPL chip emulator
OPN_Operator OPS[4];
uint8_t fbalg;
uint8_t lfosens;
//! Note offset
int16_t finetune;
};
struct opnInstMeta
{
enum { Flag_Pseudo8op = 0x01, Flag_NoSound = 0x02 };
uint16_t opnno1, opnno2;
uint8_t tone;
uint8_t flags;
uint16_t ms_sound_kon; // Number of milliseconds it produces sound;
uint16_t ms_sound_koff;
double fine_tune;
};

View file

@ -0,0 +1,776 @@
/*
* libOPNMIDI is a free MIDI to WAV conversion library with OPN2 (YM2612) emulation
*
* MIDI parser and player (Original code from ADLMIDI): Copyright (c) 2010-2014 Joel Yliluoma <bisqwit@iki.fi>
* OPNMIDI Library and YM2612 support: Copyright (c) 2017-2018 Vitaly Novichkov <admin@wohlnet.ru>
*
* Library is based on the ADLMIDI, a MIDI player for Linux and Windows with OPL3 emulation:
* http://iki.fi/bisqwit/source/adlmidi.html
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "opnmidi_private.hpp"
#define MaxCards 100
#define MaxCards_STR "100"
static OPN2_Version opn2_version = {
OPNMIDI_VERSION_MAJOR,
OPNMIDI_VERSION_MINOR,
OPNMIDI_VERSION_PATCHLEVEL
};
/*---------------------------EXPORTS---------------------------*/
OPNMIDI_EXPORT struct OPN2_MIDIPlayer *opn2_init(long sample_rate)
{
OPN2_MIDIPlayer *midi_device;
midi_device = (OPN2_MIDIPlayer *)malloc(sizeof(OPN2_MIDIPlayer));
if(!midi_device)
{
OPN2MIDI_ErrorString = "Can't initialize OPNMIDI: out of memory!";
return NULL;
}
OPNMIDIplay *player = new OPNMIDIplay(static_cast<unsigned long>(sample_rate));
if(!player)
{
free(midi_device);
OPN2MIDI_ErrorString = "Can't initialize OPNMIDI: out of memory!";
return NULL;
}
midi_device->opn2_midiPlayer = player;
opn2RefreshNumCards(midi_device);
return midi_device;
}
OPNMIDI_EXPORT int opn2_setNumChips(OPN2_MIDIPlayer *device, int numCards)
{
if(device == NULL)
return -2;
OPNMIDIplay *play = reinterpret_cast<OPNMIDIplay *>(device->opn2_midiPlayer);
play->m_setup.NumCards = static_cast<unsigned int>(numCards);
if(play->m_setup.NumCards < 1 || play->m_setup.NumCards > MaxCards)
{
play->setErrorString("number of chips may only be 1.." MaxCards_STR ".\n");
return -1;
}
play->opn.NumCards = play->m_setup.NumCards;
opn2_reset(device);
return opn2RefreshNumCards(device);
}
OPNMIDI_EXPORT int opn2_getNumChips(struct OPN2_MIDIPlayer *device)
{
if(device == NULL)
return -2;
OPNMIDIplay *play = reinterpret_cast<OPNMIDIplay *>(device->opn2_midiPlayer);
if(play)
return (int)play->m_setup.NumCards;
return -2;
}
OPNMIDI_EXPORT int opn2_openBankFile(OPN2_MIDIPlayer *device, const char *filePath)
{
if(device && device->opn2_midiPlayer)
{
OPNMIDIplay *play = reinterpret_cast<OPNMIDIplay *>(device->opn2_midiPlayer);
play->m_setup.tick_skip_samples_delay = 0;
if(!play->LoadBank(filePath))
{
std::string err = play->getErrorString();
if(err.empty())
play->setErrorString("OPN2 MIDI: Can't load file");
return -1;
}
else return opn2RefreshNumCards(device);
}
OPN2MIDI_ErrorString = "Can't load file: OPN2 MIDI is not initialized";
return -1;
}
OPNMIDI_EXPORT int opn2_openBankData(OPN2_MIDIPlayer *device, const void *mem, long size)
{
if(device && device->opn2_midiPlayer)
{
OPNMIDIplay *play = reinterpret_cast<OPNMIDIplay *>(device->opn2_midiPlayer);
play->m_setup.tick_skip_samples_delay = 0;
if(!play->LoadBank(mem, static_cast<size_t>(size)))
{
std::string err = play->getErrorString();
if(err.empty())
play->setErrorString("OPN2 MIDI: Can't load data from memory");
return -1;
}
else return 0;
}
OPN2MIDI_ErrorString = "Can't load file: OPN2 MIDI is not initialized";
return -1;
}
OPNMIDI_EXPORT void opn2_setScaleModulators(OPN2_MIDIPlayer *device, int smod)
{
if(!device) return;
OPNMIDIplay *play = reinterpret_cast<OPNMIDIplay *>(device->opn2_midiPlayer);
play->m_setup.ScaleModulators = smod;
play->opn.ScaleModulators = play->m_setup.ScaleModulators;
}
OPNMIDI_EXPORT void opn2_setLoopEnabled(OPN2_MIDIPlayer *device, int loopEn)
{
if(!device) return;
OPNMIDIplay *play = reinterpret_cast<OPNMIDIplay *>(device->opn2_midiPlayer);
play->m_setup.loopingIsEnabled = (loopEn != 0);
}
OPNMIDI_EXPORT void opn2_setLogarithmicVolumes(struct OPN2_MIDIPlayer *device, int logvol)
{
if(!device) return;
OPNMIDIplay *play = reinterpret_cast<OPNMIDIplay *>(device->opn2_midiPlayer);
play->m_setup.LogarithmicVolumes = static_cast<unsigned int>(logvol);
play->opn.LogarithmicVolumes = play->m_setup.LogarithmicVolumes;
}
OPNMIDI_EXPORT void opn2_setVolumeRangeModel(OPN2_MIDIPlayer *device, int volumeModel)
{
if(!device) return;
OPNMIDIplay *play = reinterpret_cast<OPNMIDIplay *>(device->opn2_midiPlayer);
play->m_setup.VolumeModel = volumeModel;
play->opn.ChangeVolumeRangesModel(static_cast<OPNMIDI_VolumeModels>(volumeModel));
}
OPNMIDI_EXPORT int opn2_openFile(OPN2_MIDIPlayer *device, const char *filePath)
{
if(device && device->opn2_midiPlayer)
{
OPNMIDIplay *play = reinterpret_cast<OPNMIDIplay *>(device->opn2_midiPlayer);
play->m_setup.tick_skip_samples_delay = 0;
if(!play->LoadMIDI(filePath))
{
std::string err = play->getErrorString();
if(err.empty())
play->setErrorString("OPN2 MIDI: Can't load file");
return -1;
}
else return 0;
}
OPN2MIDI_ErrorString = "Can't load file: OPN2 MIDI is not initialized";
return -1;
}
OPNMIDI_EXPORT int opn2_openData(OPN2_MIDIPlayer *device, const void *mem, unsigned long size)
{
if(device && device->opn2_midiPlayer)
{
OPNMIDIplay *play = reinterpret_cast<OPNMIDIplay *>(device->opn2_midiPlayer);
play->m_setup.tick_skip_samples_delay = 0;
if(!play->LoadMIDI(mem, static_cast<size_t>(size)))
{
std::string err = play->getErrorString();
if(err.empty())
play->setErrorString("OPN2 MIDI: Can't load data from memory");
return -1;
}
else return 0;
}
OPN2MIDI_ErrorString = "Can't load file: OPN2 MIDI is not initialized";
return -1;
}
OPNMIDI_EXPORT const char *opn2_emulatorName()
{
#ifdef USE_LEGACY_EMULATOR
return "GENS 2.10 YM2612";
#else
return "Nuked OPN2 YM3438";
#endif
}
OPNMIDI_EXPORT const char *opn2_linkedLibraryVersion()
{
return OPNMIDI_VERSION;
}
OPNMIDI_EXPORT const OPN2_Version *opn2_linkedVersion()
{
return &opn2_version;
}
OPNMIDI_EXPORT const char *opn2_errorString()
{
return OPN2MIDI_ErrorString.c_str();
}
OPNMIDI_EXPORT const char *opn2_errorInfo(struct OPN2_MIDIPlayer *device)
{
if(!device)
return opn2_errorString();
OPNMIDIplay *play = reinterpret_cast<OPNMIDIplay *>(device->opn2_midiPlayer);
if(!play)
return opn2_errorString();
return play->getErrorString().c_str();
}
OPNMIDI_EXPORT const char *opn2_getMusicTitle(struct OPN2_MIDIPlayer *device)
{
if(!device)
return "";
OPNMIDIplay *play = reinterpret_cast<OPNMIDIplay *>(device->opn2_midiPlayer);
if(!play)
return "";
return play->musTitle.c_str();
}
OPNMIDI_EXPORT void opn2_close(OPN2_MIDIPlayer *device)
{
if(device->opn2_midiPlayer)
delete reinterpret_cast<OPNMIDIplay *>(device->opn2_midiPlayer);
device->opn2_midiPlayer = NULL;
free(device);
device = NULL;
}
OPNMIDI_EXPORT void opn2_reset(OPN2_MIDIPlayer *device)
{
if(!device)
return;
OPNMIDIplay *play = reinterpret_cast<OPNMIDIplay *>(device->opn2_midiPlayer);
play->m_setup.tick_skip_samples_delay = 0;
play->opn.Reset(play->m_setup.PCM_RATE);
play->ch.clear();
play->ch.resize(play->opn.NumChannels);
}
OPNMIDI_EXPORT double opn2_totalTimeLength(struct OPN2_MIDIPlayer *device)
{
if(!device)
return -1.0;
OPNMIDIplay *play = reinterpret_cast<OPNMIDIplay *>(device->opn2_midiPlayer);
if(play)
return play->timeLength();
else
return -1.0;
}
OPNMIDI_EXPORT double opn2_loopStartTime(struct OPN2_MIDIPlayer *device)
{
if(!device)
return -1.0;
OPNMIDIplay *play = reinterpret_cast<OPNMIDIplay *>(device->opn2_midiPlayer);
if(play)
return play->getLoopStart();
else
return -1.0;
}
OPNMIDI_EXPORT double opn2_loopEndTime(struct OPN2_MIDIPlayer *device)
{
if(!device)
return -1.0;
OPNMIDIplay *play = reinterpret_cast<OPNMIDIplay *>(device->opn2_midiPlayer);
if(play)
return play->getLoopEnd();
else
return -1.0;
}
OPNMIDI_EXPORT double opn2_positionTell(struct OPN2_MIDIPlayer *device)
{
if(!device)
return -1.0;
OPNMIDIplay *play = reinterpret_cast<OPNMIDIplay *>(device->opn2_midiPlayer);
if(play)
return play->tell();
else
return -1.0;
}
OPNMIDI_EXPORT void opn2_positionSeek(struct OPN2_MIDIPlayer *device, double seconds)
{
if(!device)
return;
OPNMIDIplay *play = reinterpret_cast<OPNMIDIplay *>(device->opn2_midiPlayer);
if(play)
return play->seek(seconds);
}
OPNMIDI_EXPORT void opn2_positionRewind(struct OPN2_MIDIPlayer *device)
{
if(!device)
return;
OPNMIDIplay *play = reinterpret_cast<OPNMIDIplay *>(device->opn2_midiPlayer);
if(play)
return play->rewind();
}
OPNMIDI_EXPORT void opn2_setTempo(struct OPN2_MIDIPlayer *device, double tempo)
{
if(!device || (tempo <= 0.0))
return;
OPNMIDIplay *play = reinterpret_cast<OPNMIDIplay *>(device->opn2_midiPlayer);
if(play)
return play->setTempo(tempo);
}
OPNMIDI_EXPORT const char *opn2_metaMusicTitle(struct OPN2_MIDIPlayer *device)
{
if(!device)
return "";
OPNMIDIplay *play = reinterpret_cast<OPNMIDIplay *>(device->opn2_midiPlayer);
if(play)
return play->musTitle.c_str();
else
return "";
}
OPNMIDI_EXPORT const char *opn2_metaMusicCopyright(struct OPN2_MIDIPlayer *device)
{
if(!device)
return "";
OPNMIDIplay *play = reinterpret_cast<OPNMIDIplay *>(device->opn2_midiPlayer);
if(play)
return play->musCopyright.c_str();
else
return "";
}
OPNMIDI_EXPORT size_t opn2_metaTrackTitleCount(struct OPN2_MIDIPlayer *device)
{
if(!device)
return 0;
OPNMIDIplay *play = reinterpret_cast<OPNMIDIplay *>(device->opn2_midiPlayer);
if(play)
return play->musTrackTitles.size();
else
return 0;
}
OPNMIDI_EXPORT const char *opn2_metaTrackTitle(struct OPN2_MIDIPlayer *device, size_t index)
{
if(!device)
return 0;
OPNMIDIplay *play = reinterpret_cast<OPNMIDIplay *>(device->opn2_midiPlayer);
if(index >= play->musTrackTitles.size())
return "INVALID";
return play->musTrackTitles[index].c_str();
}
OPNMIDI_EXPORT size_t opn2_metaMarkerCount(struct OPN2_MIDIPlayer *device)
{
if(!device)
return 0;
OPNMIDIplay *play = reinterpret_cast<OPNMIDIplay *>(device->opn2_midiPlayer);
if(play)
return play->musMarkers.size();
else
return 0;
}
OPNMIDI_EXPORT Opn2_MarkerEntry opn2_metaMarker(struct OPN2_MIDIPlayer *device, size_t index)
{
struct Opn2_MarkerEntry marker;
OPNMIDIplay *play = reinterpret_cast<OPNMIDIplay *>(device->opn2_midiPlayer);
if(!device || !play || (index >= play->musMarkers.size()))
{
marker.label = "INVALID";
marker.pos_time = 0.0;
marker.pos_ticks = 0;
return marker;
}
else
{
OPNMIDIplay::MIDI_MarkerEntry &mk = play->musMarkers[index];
marker.label = mk.label.c_str();
marker.pos_time = mk.pos_time;
marker.pos_ticks = (unsigned long)mk.pos_ticks;
}
return marker;
}
OPNMIDI_EXPORT void opn2_setRawEventHook(struct OPN2_MIDIPlayer *device, OPN2_RawEventHook rawEventHook, void *userData)
{
if(!device)
return;
OPNMIDIplay *play = reinterpret_cast<OPNMIDIplay *>(device->opn2_midiPlayer);
play->hooks.onEvent = rawEventHook;
play->hooks.onEvent_userData = userData;
}
/* Set note hook */
OPNMIDI_EXPORT void opn2_setNoteHook(struct OPN2_MIDIPlayer *device, OPN2_NoteHook noteHook, void *userData)
{
if(!device)
return;
OPNMIDIplay *play = reinterpret_cast<OPNMIDIplay *>(device->opn2_midiPlayer);
play->hooks.onNote = noteHook;
play->hooks.onNote_userData = userData;
}
/* Set debug message hook */
OPNMIDI_EXPORT void opn2_setDebugMessageHook(struct OPN2_MIDIPlayer *device, OPN2_DebugMessageHook debugMessageHook, void *userData)
{
if(!device)
return;
OPNMIDIplay *play = reinterpret_cast<OPNMIDIplay *>(device->opn2_midiPlayer);
play->hooks.onDebugMessage = debugMessageHook;
play->hooks.onDebugMessage_userData = userData;
}
inline static void SendStereoAudio(int &samples_requested,
ssize_t &in_size,
short *_in,
ssize_t out_pos,
short *_out)
{
if(!in_size)
return;
size_t offset = static_cast<size_t>(out_pos);
size_t inSamples = static_cast<size_t>(in_size * 2);
size_t maxSamples = static_cast<size_t>(samples_requested) - offset;
size_t toCopy = std::min(maxSamples, inSamples);
std::memcpy(_out + out_pos, _in, toCopy * sizeof(short));
}
OPNMIDI_EXPORT int opn2_play(OPN2_MIDIPlayer *device, int sampleCount, short *out)
{
sampleCount -= sampleCount % 2; //Avoid even sample requests
if(sampleCount < 0)
return 0;
if(!device)
return 0;
OPNMIDIplay * player = (reinterpret_cast<OPNMIDIplay *>(device->opn2_midiPlayer));
OPNMIDIplay::Setup &setup = player->m_setup;
ssize_t gotten_len = 0;
ssize_t n_periodCountStereo = 512;
//ssize_t n_periodCountPhys = n_periodCountStereo * 2;
int left = sampleCount;
bool hasSkipped = setup.tick_skip_samples_delay > 0;
while(left > 0)
{
{//
const double eat_delay = setup.delay < setup.maxdelay ? setup.delay : setup.maxdelay;
if(hasSkipped)
{
size_t samples = setup.tick_skip_samples_delay > sampleCount ? sampleCount : setup.tick_skip_samples_delay;
n_periodCountStereo = samples / 2;
}
else
{
setup.delay -= eat_delay;
setup.carry += setup.PCM_RATE * eat_delay;
n_periodCountStereo = static_cast<ssize_t>(setup.carry);
setup.carry -= n_periodCountStereo;
}
//if(setup.SkipForward > 0)
// setup.SkipForward -= 1;
//else
{
if((player->atEnd) && (setup.delay <= 0.0))
break;//Stop to fetch samples at reaching the song end with disabled loop
ssize_t leftSamples = left / 2;
if(n_periodCountStereo > leftSamples)
{
setup.tick_skip_samples_delay = (n_periodCountStereo - leftSamples) * 2;
n_periodCountStereo = leftSamples;
}
//! Count of stereo samples
ssize_t in_generatedStereo = (n_periodCountStereo > 512) ? 512 : n_periodCountStereo;
//! Total count of samples
ssize_t in_generatedPhys = in_generatedStereo * 2;
//! Unsigned total sample count
//fill buffer with zeros
int16_t *out_buf = player->outBuf;
std::memset(out_buf, 0, static_cast<size_t>(in_generatedPhys) * sizeof(int16_t));
unsigned int chips = player->opn.NumCards;
if(chips == 1)
{
#ifdef USE_LEGACY_EMULATOR
player->opn.cardsOP2[0]->run(int(in_generatedStereo), out_buf);
#else
OPN2_GenerateStream(player->opn.cardsOP2[0], out_buf, (Bit32u)in_generatedStereo);
#endif
}
else/* if(n_periodCountStereo > 0)*/
{
/* Generate data from every chip and mix result */
for(unsigned card = 0; card < chips; ++card)
{
#ifdef USE_LEGACY_EMULATOR
player->opn.cardsOP2[card]->run(int(in_generatedStereo), out_buf);
#else
OPN2_GenerateStreamMix(player->opn.cardsOP2[card], out_buf, (Bit32u)in_generatedStereo);
#endif
}
}
/* Process it */
SendStereoAudio(sampleCount, in_generatedStereo, out_buf, gotten_len, out);
left -= (int)in_generatedPhys;
gotten_len += (in_generatedPhys) /* - setup.stored_samples*/;
}
if(hasSkipped)
{
setup.tick_skip_samples_delay -= n_periodCountStereo * 2;
hasSkipped = setup.tick_skip_samples_delay > 0;
}
else
setup.delay = player->Tick(eat_delay, setup.mindelay);
}//
}
return static_cast<int>(gotten_len);
}
OPNMIDI_EXPORT int opn2_generate(struct OPN2_MIDIPlayer *device, int sampleCount, short *out)
{
sampleCount -= sampleCount % 2; //Avoid even sample requests
if(sampleCount < 0)
return 0;
if(!device)
return 0;
OPNMIDIplay * player = (reinterpret_cast<OPNMIDIplay *>(device->opn2_midiPlayer));
OPNMIDIplay::Setup &setup = player->m_setup;
ssize_t gotten_len = 0;
ssize_t n_periodCountStereo = 512;
int left = sampleCount;
double delay = double(sampleCount) / double(setup.PCM_RATE);
while(left > 0)
{
{//
const double eat_delay = delay < setup.maxdelay ? delay : setup.maxdelay;
delay -= eat_delay;
setup.carry += setup.PCM_RATE * eat_delay;
n_periodCountStereo = static_cast<ssize_t>(setup.carry);
setup.carry -= n_periodCountStereo;
{
ssize_t leftSamples = left / 2;
if(n_periodCountStereo > leftSamples)
n_periodCountStereo = leftSamples;
//! Count of stereo samples
ssize_t in_generatedStereo = (n_periodCountStereo > 512) ? 512 : n_periodCountStereo;
//! Total count of samples
ssize_t in_generatedPhys = in_generatedStereo * 2;
//! Unsigned total sample count
//fill buffer with zeros
int16_t *out_buf = player->outBuf;
std::memset(out_buf, 0, static_cast<size_t>(in_generatedPhys) * sizeof(int16_t));
unsigned int chips = player->opn.NumCards;
if(chips == 1)
{
#ifdef USE_LEGACY_EMULATOR
player->opn.cardsOP2[0]->run(int(in_generatedStereo), out_buf);
#else
OPN2_GenerateStream(player->opn.cardsOP2[0], out_buf, (Bit32u)in_generatedStereo);
#endif
}
else/* if(n_periodCountStereo > 0)*/
{
/* Generate data from every chip and mix result */
for(unsigned card = 0; card < chips; ++card)
{
#ifdef USE_LEGACY_EMULATOR
player->opn.cardsOP2[card]->run(int(in_generatedStereo), out_buf);
#else
OPN2_GenerateStreamMix(player->opn.cardsOP2[card], out_buf, (Bit32u)in_generatedStereo);
#endif
}
}
/* Process it */
SendStereoAudio(sampleCount, in_generatedStereo, out_buf, gotten_len, out);
left -= (int)in_generatedPhys;
gotten_len += (in_generatedPhys) /* - setup.stored_samples*/;
}
player->TickIteratos(eat_delay);
}//
}
return static_cast<int>(gotten_len);
}
OPNMIDI_EXPORT double opn2_tickEvents(struct OPN2_MIDIPlayer *device, double seconds, double granuality)
{
if(!device)
return -1.0;
OPNMIDIplay *player = reinterpret_cast<OPNMIDIplay *>(device->opn2_midiPlayer);
if(!player)
return -1.0;
return player->Tick(seconds, granuality);
}
OPNMIDI_EXPORT int opn2_atEnd(struct OPN2_MIDIPlayer *device)
{
if(!device)
return 1;
OPNMIDIplay *player = reinterpret_cast<OPNMIDIplay *>(device->opn2_midiPlayer);
if(!player)
return 1;
return (int)player->atEnd;
}
OPNMIDI_EXPORT void opn2_panic(struct OPN2_MIDIPlayer *device)
{
if(!device)
return;
OPNMIDIplay *player = reinterpret_cast<OPNMIDIplay *>(device->opn2_midiPlayer);
if(!player)
return;
player->realTime_panic();
}
OPNMIDI_EXPORT void opn2_rt_resetState(struct OPN2_MIDIPlayer *device)
{
if(!device)
return;
OPNMIDIplay *player = reinterpret_cast<OPNMIDIplay *>(device->opn2_midiPlayer);
if(!player)
return;
player->realTime_ResetState();
}
OPNMIDI_EXPORT int opn2_rt_noteOn(struct OPN2_MIDIPlayer *device, OPN2_UInt8 channel, OPN2_UInt8 note, OPN2_UInt8 velocity)
{
if(!device)
return 0;
OPNMIDIplay *player = reinterpret_cast<OPNMIDIplay *>(device->opn2_midiPlayer);
if(!player)
return 0;
return (int)player->realTime_NoteOn(channel, note, velocity);
}
OPNMIDI_EXPORT void opn2_rt_noteOff(struct OPN2_MIDIPlayer *device, OPN2_UInt8 channel, OPN2_UInt8 note)
{
if(!device)
return;
OPNMIDIplay *player = reinterpret_cast<OPNMIDIplay *>(device->opn2_midiPlayer);
if(!player)
return;
player->realTime_NoteOff(channel, note);
}
OPNMIDI_EXPORT void opn2_rt_noteAfterTouch(struct OPN2_MIDIPlayer *device, OPN2_UInt8 channel, OPN2_UInt8 note, OPN2_UInt8 atVal)
{
if(!device)
return;
OPNMIDIplay *player = reinterpret_cast<OPNMIDIplay *>(device->opn2_midiPlayer);
if(!player)
return;
player->realTime_NoteAfterTouch(channel, note, atVal);
}
OPNMIDI_EXPORT void opn2_rt_channelAfterTouch(struct OPN2_MIDIPlayer *device, OPN2_UInt8 channel, OPN2_UInt8 atVal)
{
if(!device)
return;
OPNMIDIplay *player = reinterpret_cast<OPNMIDIplay *>(device->opn2_midiPlayer);
if(!player)
return;
player->realTime_ChannelAfterTouch(channel, atVal);
}
OPNMIDI_EXPORT void opn2_rt_controllerChange(struct OPN2_MIDIPlayer *device, OPN2_UInt8 channel, OPN2_UInt8 type, OPN2_UInt8 value)
{
if(!device)
return;
OPNMIDIplay *player = reinterpret_cast<OPNMIDIplay *>(device->opn2_midiPlayer);
if(!player)
return;
player->realTime_Controller(channel, type, value);
}
OPNMIDI_EXPORT void opn2_rt_patchChange(struct OPN2_MIDIPlayer *device, OPN2_UInt8 channel, OPN2_UInt8 patch)
{
if(!device)
return;
OPNMIDIplay *player = reinterpret_cast<OPNMIDIplay *>(device->opn2_midiPlayer);
if(!player)
return;
player->realTime_PatchChange(channel, patch);
}
OPNMIDI_EXPORT void opn2_rt_pitchBend(struct OPN2_MIDIPlayer *device, OPN2_UInt8 channel, OPN2_UInt16 pitch)
{
if(!device)
return;
OPNMIDIplay *player = reinterpret_cast<OPNMIDIplay *>(device->opn2_midiPlayer);
if(!player)
return;
player->realTime_PitchBend(channel, pitch);
}
OPNMIDI_EXPORT void opn2_rt_pitchBendML(struct OPN2_MIDIPlayer *device, OPN2_UInt8 channel, OPN2_UInt8 msb, OPN2_UInt8 lsb)
{
if(!device)
return;
OPNMIDIplay *player = reinterpret_cast<OPNMIDIplay *>(device->opn2_midiPlayer);
if(!player)
return;
player->realTime_PitchBend(channel, msb, lsb);
}
OPNMIDI_EXPORT void opn2_rt_bankChangeLSB(struct OPN2_MIDIPlayer *device, OPN2_UInt8 channel, OPN2_UInt8 lsb)
{
if(!device)
return;
OPNMIDIplay *player = reinterpret_cast<OPNMIDIplay *>(device->opn2_midiPlayer);
if(!player)
return;
player->realTime_BankChangeLSB(channel, lsb);
}
OPNMIDI_EXPORT void opn2_rt_bankChangeMSB(struct OPN2_MIDIPlayer *device, OPN2_UInt8 channel, OPN2_UInt8 msb)
{
if(!device)
return;
OPNMIDIplay *player = reinterpret_cast<OPNMIDIplay *>(device->opn2_midiPlayer);
if(!player)
return;
player->realTime_BankChangeMSB(channel, msb);
}
OPNMIDI_EXPORT void opn2_rt_bankChange(struct OPN2_MIDIPlayer *device, OPN2_UInt8 channel, OPN2_SInt16 bank)
{
if(!device)
return;
OPNMIDIplay *player = reinterpret_cast<OPNMIDIplay *>(device->opn2_midiPlayer);
if(!player)
return;
player->realTime_BankChange(channel, (uint16_t)bank);
}

266
src/sound/opnmidi/opnmidi.h Normal file
View file

@ -0,0 +1,266 @@
/*
* libOPNMIDI is a free MIDI to WAV conversion library with OPN2 (YM2612) emulation
*
* MIDI parser and player (Original code from ADLMIDI): Copyright (c) 2010-2014 Joel Yliluoma <bisqwit@iki.fi>
* OPNMIDI Library and YM2612 support: Copyright (c) 2017-2018 Vitaly Novichkov <admin@wohlnet.ru>
*
* Library is based on the ADLMIDI, a MIDI player for Linux and Windows with OPL3 emulation:
* http://iki.fi/bisqwit/source/adlmidi.html
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef OPNMIDI_H
#define OPNMIDI_H
#ifdef __cplusplus
extern "C" {
#endif
#define OPNMIDI_VERSION_MAJOR 1
#define OPNMIDI_VERSION_MINOR 1
#define OPNMIDI_VERSION_PATCHLEVEL 0
#define OPNMIDI_TOSTR(s) #s
#define OPNMIDI_VERSION \
OPNMIDI_TOSTR(OPNMIDI_VERSION_MAJOR) "." \
OPNMIDI_TOSTR(OPNMIDI_VERSION_MINOR) "." \
OPNMIDI_TOSTR(OPNMIDI_VERSION_PATCHLEVEL)
#include <stddef.h>
#if defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L)
#include <stdint.h>
typedef uint8_t OPN2_UInt8;
typedef uint16_t OPN2_UInt16;
typedef int8_t OPN2_SInt8;
typedef int16_t OPN2_SInt16;
#else
typedef unsigned char OPN2_UInt8;
typedef unsigned short OPN2_UInt16;
typedef char OPN2_SInt8;
typedef short OPN2_SInt16;
#endif
enum OPNMIDI_VolumeModels
{
OPNMIDI_VolumeModel_AUTO = 0,
OPNMIDI_VolumeModel_Generic,
OPNMIDI_VolumeModel_CMF,
OPNMIDI_VolumeModel_DMX,
OPNMIDI_VolumeModel_APOGEE,
OPNMIDI_VolumeModel_9X
};
struct OPN2_MIDIPlayer
{
void *opn2_midiPlayer;
};
/* DEPRECATED */
#define opn2_setNumCards opn2_setNumChips
/* Sets number of emulated sound cards (from 1 to 100). Emulation of multiple sound cards exchanges polyphony limits*/
extern int opn2_setNumChips(struct OPN2_MIDIPlayer *device, int numCards);
/* Get current number of emulated chips */
extern int opn2_getNumChips(struct OPN2_MIDIPlayer *device);
/*Enable or disable Enables scaling of modulator volumes*/
extern void opn2_setScaleModulators(struct OPN2_MIDIPlayer *device, int smod);
/*Enable or disable built-in loop (built-in loop supports 'loopStart' and 'loopEnd' tags to loop specific part)*/
extern void opn2_setLoopEnabled(struct OPN2_MIDIPlayer *device, int loopEn);
/*Enable or disable Logariphmic volume changer (-1 sets default per bank, 0 disable, 1 enable) */
extern void opn2_setLogarithmicVolumes(struct OPN2_MIDIPlayer *device, int logvol);
/*Set different volume range model */
extern void opn2_setVolumeRangeModel(struct OPN2_MIDIPlayer *device, int volumeModel);
/*Load WOPN bank file from File System. Is recommended to call adl_reset() to apply changes to already-loaded file player or real-time.*/
extern int opn2_openBankFile(struct OPN2_MIDIPlayer *device, const char *filePath);
/*Load WOPN bank file from memory data*/
extern int opn2_openBankData(struct OPN2_MIDIPlayer *device, const void *mem, long size);
/*Returns chip emulator name string*/
extern const char *opn2_emulatorName();
typedef struct {
OPN2_UInt16 major;
OPN2_UInt16 minor;
OPN2_UInt16 patch;
} OPN2_Version;
/*Returns string which contains a version number*/
extern const char *opn2_linkedLibraryVersion();
/*Returns structure which contains a version number of library */
extern const OPN2_Version *opn2_linkedVersion();
/*Returns string which contains last error message*/
extern const char *opn2_errorString();
/*Returns string which contains last error message on specific device*/
extern const char *opn2_errorInfo(struct OPN2_MIDIPlayer *device);
/*Initialize ADLMIDI Player device*/
extern struct OPN2_MIDIPlayer *opn2_init(long sample_rate);
/*Load MIDI file from File System*/
extern int opn2_openFile(struct OPN2_MIDIPlayer *device, const char *filePath);
/*Load MIDI file from memory data*/
extern int opn2_openData(struct OPN2_MIDIPlayer *device, const void *mem, unsigned long size);
/*Resets MIDI player*/
extern void opn2_reset(struct OPN2_MIDIPlayer *device);
/*Get total time length of current song*/
extern double opn2_totalTimeLength(struct OPN2_MIDIPlayer *device);
/*Get loop start time if presented. -1 means MIDI file has no loop points */
extern double opn2_loopStartTime(struct OPN2_MIDIPlayer *device);
/*Get loop end time if presented. -1 means MIDI file has no loop points */
extern double opn2_loopEndTime(struct OPN2_MIDIPlayer *device);
/*Get current time position in seconds*/
extern double opn2_positionTell(struct OPN2_MIDIPlayer *device);
/*Jump to absolute time position in seconds*/
extern void opn2_positionSeek(struct OPN2_MIDIPlayer *device, double seconds);
/*Reset MIDI track position to begin */
extern void opn2_positionRewind(struct OPN2_MIDIPlayer *device);
/*Set tempo multiplier: 1.0 - original tempo, >1 - play faster, <1 - play slower */
extern void opn2_setTempo(struct OPN2_MIDIPlayer *device, double tempo);
/*Close and delete OPNMIDI device*/
extern void opn2_close(struct OPN2_MIDIPlayer *device);
/**META**/
/*Returns string which contains a music title*/
extern const char *opn2_metaMusicTitle(struct OPN2_MIDIPlayer *device);
/*Returns string which contains a copyright string*/
extern const char *opn2_metaMusicCopyright(struct OPN2_MIDIPlayer *device);
/*Returns count of available track titles: NOTE: there are CAN'T be associated with channel in any of event or note hooks */
extern size_t opn2_metaTrackTitleCount(struct OPN2_MIDIPlayer *device);
/*Get track title by index*/
extern const char *opn2_metaTrackTitle(struct OPN2_MIDIPlayer *device, size_t index);
struct Opn2_MarkerEntry
{
const char *label;
double pos_time;
unsigned long pos_ticks;
};
/*Returns count of available markers*/
extern size_t opn2_metaMarkerCount(struct OPN2_MIDIPlayer *device);
/*Returns the marker entry*/
extern struct Opn2_MarkerEntry opn2_metaMarker(struct OPN2_MIDIPlayer *device, size_t index);
/*Take a sample buffer and iterate MIDI timers */
extern int opn2_play(struct OPN2_MIDIPlayer *device, int sampleCount, short out[]);
/*Generate audio output from chip emulators without iteration of MIDI timers.*/
extern int opn2_generate(struct OPN2_MIDIPlayer *device, int sampleCount, short *out);
/**
* @brief Periodic tick handler.
* @param device
* @param seconds seconds since last call
* @param granularity don't expect intervals smaller than this, in seconds
* @return desired number of seconds until next call
*
* Use it for Hardware OPL3 mode or when you want to process events differently from opn2_play() function.
* DON'T USE IT TOGETHER WITH opn2_play()!!!
*/
extern double opn2_tickEvents(struct OPN2_MIDIPlayer *device, double seconds, double granuality);
/*Returns 1 if music position has reached end*/
extern int opn2_atEnd(struct OPN2_MIDIPlayer *device);
/**RealTime**/
/*Force Off all notes on all channels*/
extern void opn2_panic(struct OPN2_MIDIPlayer *device);
/*Reset states of all controllers on all MIDI channels*/
extern void opn2_rt_resetState(struct OPN2_MIDIPlayer *device);
/*Turn specific MIDI note ON*/
extern int opn2_rt_noteOn(struct OPN2_MIDIPlayer *device, OPN2_UInt8 channel, OPN2_UInt8 note, OPN2_UInt8 velocity);
/*Turn specific MIDI note OFF*/
extern void opn2_rt_noteOff(struct OPN2_MIDIPlayer *device, OPN2_UInt8 channel, OPN2_UInt8 note);
/*Set note after-touch*/
extern void opn2_rt_noteAfterTouch(struct OPN2_MIDIPlayer *device, OPN2_UInt8 channel, OPN2_UInt8 note, OPN2_UInt8 atVal);
/*Set channel after-touch*/
extern void opn2_rt_channelAfterTouch(struct OPN2_MIDIPlayer *device, OPN2_UInt8 channel, OPN2_UInt8 atVal);
/*Apply controller change*/
extern void opn2_rt_controllerChange(struct OPN2_MIDIPlayer *device, OPN2_UInt8 channel, OPN2_UInt8 type, OPN2_UInt8 value);
/*Apply patch change*/
extern void opn2_rt_patchChange(struct OPN2_MIDIPlayer *device, OPN2_UInt8 channel, OPN2_UInt8 patch);
/*Apply pitch bend change*/
extern void opn2_rt_pitchBend(struct OPN2_MIDIPlayer *device, OPN2_UInt8 channel, OPN2_UInt16 pitch);
/*Apply pitch bend change*/
extern void opn2_rt_pitchBendML(struct OPN2_MIDIPlayer *device, OPN2_UInt8 channel, OPN2_UInt8 msb, OPN2_UInt8 lsb);
/*Change LSB of the bank*/
extern void opn2_rt_bankChangeLSB(struct OPN2_MIDIPlayer *device, OPN2_UInt8 channel, OPN2_UInt8 lsb);
/*Change MSB of the bank*/
extern void opn2_rt_bankChangeMSB(struct OPN2_MIDIPlayer *device, OPN2_UInt8 channel, OPN2_UInt8 msb);
/*Change bank by absolute signed value*/
extern void opn2_rt_bankChange(struct OPN2_MIDIPlayer *device, OPN2_UInt8 channel, OPN2_SInt16 bank);
/**Hooks**/
typedef void (*OPN2_RawEventHook)(void *userdata, OPN2_UInt8 type, OPN2_UInt8 subtype, OPN2_UInt8 channel, const OPN2_UInt8 *data, size_t len);
typedef void (*OPN2_NoteHook)(void *userdata, int adlchn, int note, int ins, int pressure, double bend);
typedef void (*OPN2_DebugMessageHook)(void *userdata, const char *fmt, ...);
/* Set raw MIDI event hook */
extern void opn2_setRawEventHook(struct OPN2_MIDIPlayer *device, OPN2_RawEventHook rawEventHook, void *userData);
/* Set note hook */
extern void opn2_setNoteHook(struct OPN2_MIDIPlayer *device, OPN2_NoteHook noteHook, void *userData);
/* Set debug message hook */
extern void opn2_setDebugMessageHook(struct OPN2_MIDIPlayer *device, OPN2_DebugMessageHook debugMessageHook, void *userData);
#ifdef __cplusplus
}
#endif
#endif /* OPNMIDI_H */

View file

@ -0,0 +1,557 @@
/*
* libOPNMIDI is a free MIDI to WAV conversion library with OPN2 (YM2612) emulation
*
* MIDI parser and player (Original code from ADLMIDI): Copyright (c) 2010-2014 Joel Yliluoma <bisqwit@iki.fi>
* OPNMIDI Library and YM2612 support: Copyright (c) 2017-2018 Vitaly Novichkov <admin@wohlnet.ru>
*
* Library is based on the ADLMIDI, a MIDI player for Linux and Windows with OPL3 emulation:
* http://iki.fi/bisqwit/source/adlmidi.html
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "opnmidi_private.hpp"
#include "opnmidi_mus2mid.h"
#include "opnmidi_xmi2mid.h"
uint64_t OPNMIDIplay::ReadBEint(const void *buffer, size_t nbytes)
{
uint64_t result = 0;
const unsigned char *data = reinterpret_cast<const unsigned char *>(buffer);
for(unsigned n = 0; n < nbytes; ++n)
result = (result << 8) + data[n];
return result;
}
uint64_t OPNMIDIplay::ReadLEint(const void *buffer, size_t nbytes)
{
uint64_t result = 0;
const unsigned char *data = reinterpret_cast<const unsigned char *>(buffer);
for(unsigned n = 0; n < nbytes; ++n)
result = result + static_cast<uint64_t>(data[n] << (n * 8));
return result;
}
//uint64_t OPNMIDIplay::ReadVarLenEx(size_t tk, bool &ok)
//{
// uint64_t result = 0;
// ok = false;
// for(;;)
// {
// if(tk >= TrackData.size())
// return 1;
// if(tk >= CurrentPosition.track.size())
// return 2;
// size_t ptr = CurrentPosition.track[tk].ptr;
// if(ptr >= TrackData[tk].size())
// return 3;
// unsigned char byte = TrackData[tk][CurrentPosition.track[tk].ptr++];
// result = (result << 7) + (byte & 0x7F);
// if(!(byte & 0x80)) break;
// }
// ok = true;
// return result;
//}
bool OPNMIDIplay::LoadBank(const std::string &filename)
{
fileReader file;
file.openFile(filename.c_str());
return LoadBank(file);
}
bool OPNMIDIplay::LoadBank(const void *data, size_t size)
{
fileReader file;
file.openData(data, (size_t)size);
return LoadBank(file);
}
size_t readU16BE(OPNMIDIplay::fileReader &fr, uint16_t &out)
{
uint8_t arr[2];
size_t ret = fr.read(arr, 1, 2);
out = arr[1];
out |= ((arr[0] << 8) & 0xFF00);
return ret;
}
size_t readS16BE(OPNMIDIplay::fileReader &fr, int16_t &out)
{
uint8_t arr[2];
size_t ret = fr.read(arr, 1, 2);
out = *reinterpret_cast<signed char *>(&arr[0]);
out *= 1 << 8;
out |= arr[1];
return ret;
}
int16_t toSint16BE(uint8_t *arr)
{
int16_t num = *reinterpret_cast<const int8_t *>(&arr[0]);
num *= 1 << 8;
num |= arr[1];
return num;
}
static uint16_t toUint16LE(const uint8_t *arr)
{
uint16_t num = arr[0];
num |= ((arr[1] << 8) & 0xFF00);
return num;
}
static uint16_t toUint16BE(const uint8_t *arr)
{
uint16_t num = arr[1];
num |= ((arr[0] << 8) & 0xFF00);
return num;
}
static const char *wopn2_magic1 = "WOPN2-BANK\0";
static const char *wopn2_magic2 = "WOPN2-B2NK\0";
#define WOPL_INST_SIZE_V1 65
#define WOPL_INST_SIZE_V2 69
static const uint16_t latest_version = 2;
bool OPNMIDIplay::LoadBank(OPNMIDIplay::fileReader &fr)
{
size_t fsize;
ADL_UNUSED(fsize);
if(!fr.isValid())
{
errorStringOut = "Can't load bank file: Invalid data stream!";
return false;
}
char magic[32];
std::memset(magic, 0, 32);
uint16_t version = 1;
uint16_t count_melodic_banks = 1;
uint16_t count_percusive_banks = 1;
if(fr.read(magic, 1, 11) != 11)
{
errorStringOut = "Can't load bank file: Can't read magic number!";
return false;
}
bool is1 = std::strncmp(magic, wopn2_magic1, 11) == 0;
bool is2 = std::strncmp(magic, wopn2_magic2, 11) == 0;
if(!is1 && !is2)
{
errorStringOut = "Can't load bank file: Invalid magic number!";
return false;
}
if(is2)
{
uint8_t ver[2];
if(fr.read(ver, 1, 2) != 2)
{
errorStringOut = "Can't load bank file: Can't read version number!";
return false;
}
version = toUint16LE(ver);
if(version < 2 || version > latest_version)
{
errorStringOut = "Can't load bank file: unsupported WOPN version!";
return false;
}
}
opn.dynamic_instruments.clear();
opn.dynamic_metainstruments.clear();
if((readU16BE(fr, count_melodic_banks) != 2) || (readU16BE(fr, count_percusive_banks) != 2))
{
errorStringOut = "Can't load bank file: Can't read count of banks!";
return false;
}
if((count_melodic_banks < 1) || (count_percusive_banks < 1))
{
errorStringOut = "Custom bank: Too few banks in this file!";
return false;
}
if(fr.read(&opn.regLFO, 1, 1) != 1)
{
errorStringOut = "Can't load bank file: Can't read LFO registry state!";
return false;
}
opn.dynamic_melodic_banks.clear();
opn.dynamic_percussion_banks.clear();
if(version >= 2)//Read bank meta-entries
{
for(uint16_t i = 0; i < count_melodic_banks; i++)
{
uint8_t bank_meta[34];
if(fr.read(bank_meta, 1, 34) != 34)
{
opn.dynamic_melodic_banks.clear();
errorStringOut = "Custom bank: Fail to read melodic bank meta-data!";
return false;
}
uint16_t bank = uint16_t(bank_meta[33]) * 256 + uint16_t(bank_meta[32]);
size_t offset = opn.dynamic_melodic_banks.size();
opn.dynamic_melodic_banks[bank] = offset;
//strncpy(bankMeta.name, char_p(bank_meta), 32);
}
for(uint16_t i = 0; i < count_percusive_banks; i++)
{
uint8_t bank_meta[34];
if(fr.read(bank_meta, 1, 34) != 34)
{
opn.dynamic_melodic_banks.clear();
opn.dynamic_percussion_banks.clear();
errorStringOut = "Custom bank: Fail to read percussion bank meta-data!";
return false;
}
uint16_t bank = uint16_t(bank_meta[33]) * 256 + uint16_t(bank_meta[32]);
size_t offset = opn.dynamic_percussion_banks.size();
opn.dynamic_percussion_banks[bank] = offset;
//strncpy(bankMeta.name, char_p(bank_meta), 32);
}
}
opn.dynamic_percussion_offset = count_melodic_banks * 128;
uint16_t total = 128 * count_melodic_banks + 128 * count_percusive_banks;
for(uint16_t i = 0; i < total; i++)
{
opnInstData data;
opnInstMeta meta;
uint8_t idata[WOPL_INST_SIZE_V2];
size_t readSize = version >= 2 ? WOPL_INST_SIZE_V2 : WOPL_INST_SIZE_V1;
if(fr.read(idata, 1, readSize) != readSize)
{
opn.dynamic_instruments.clear();
opn.dynamic_metainstruments.clear();
opn.dynamic_melodic_banks.clear();
opn.dynamic_percussion_banks.clear();
errorStringOut = "Can't load bank file: Failed to read instrument data";
return false;
}
data.finetune = toSint16BE(idata + 32);
//Percussion instrument note number or a "fixed note sound"
meta.tone = idata[34];
data.fbalg = idata[35];
data.lfosens = idata[36];
for(size_t op = 0; op < 4; op++)
{
size_t off = 37 + op * 7;
std::memcpy(data.OPS[op].data, idata + off, 7);
}
if(version >= 2)
{
meta.ms_sound_kon = toUint16BE(idata + 65);
meta.ms_sound_koff = toUint16BE(idata + 67);
}
else
{
meta.ms_sound_kon = 1000;
meta.ms_sound_koff = 500;
}
meta.opnno1 = uint16_t(opn.dynamic_instruments.size());
meta.opnno2 = uint16_t(opn.dynamic_instruments.size());
/* Junk, delete later */
meta.flags = 0;
meta.fine_tune = 0.0;
/* Junk, delete later */
opn.dynamic_instruments.push_back(data);
opn.dynamic_metainstruments.push_back(meta);
}
applySetup();
return true;
}
bool OPNMIDIplay::LoadMIDI(const std::string &filename)
{
fileReader file;
file.openFile(filename.c_str());
if(!LoadMIDI(file))
return false;
return true;
}
bool OPNMIDIplay::LoadMIDI(const void *data, size_t size)
{
fileReader file;
file.openData(data, size);
return LoadMIDI(file);
}
bool OPNMIDIplay::LoadMIDI(OPNMIDIplay::fileReader &fr)
{
size_t fsize;
ADL_UNUSED(fsize);
//! Temp buffer for conversion
AdlMIDI_CPtr<uint8_t> cvt_buf;
errorString.clear();
if(opn.dynamic_instruments.empty())
{
errorStringOut = "Bank is not set! Please load any instruments bank by using of adl_openBankFile() or adl_openBankData() functions!";
return false;
}
if(!fr.isValid())
{
errorStringOut = "Invalid data stream!\n";
#ifndef _WIN32
errorStringOut += std::strerror(errno);
#endif
return false;
}
/**** Set all properties BEFORE starting of actial file reading! ****/
applySetup();
atEnd = false;
loopStart = true;
invalidLoop = false;
bool is_GMF = false; // GMD/MUS files (ScummVM)
bool is_RSXX = false; // RSXX, such as Cartooners
const size_t HeaderSize = 4 + 4 + 2 + 2 + 2; // 14
char HeaderBuf[HeaderSize] = "";
size_t DeltaTicks = 192, TrackCount = 1;
riffskip:
fsize = fr.read(HeaderBuf, 1, HeaderSize);
if(std::memcmp(HeaderBuf, "RIFF", 4) == 0)
{
fr.seek(6l, SEEK_CUR);
goto riffskip;
}
if(std::memcmp(HeaderBuf, "GMF\x1", 4) == 0)
{
// GMD/MUS files (ScummVM)
fr.seek(7 - static_cast<long>(HeaderSize), SEEK_CUR);
is_GMF = true;
}
else if(std::memcmp(HeaderBuf, "MUS\x1A", 4) == 0)
{
// MUS/DMX files (Doom)
fr.seek(0, SEEK_END);
size_t mus_len = fr.tell();
fr.seek(0, SEEK_SET);
uint8_t *mus = (uint8_t *)malloc(mus_len);
if(!mus)
{
errorStringOut = "Out of memory!";
return false;
}
fr.read(mus, 1, mus_len);
//Close source stream
fr.close();
uint8_t *mid = NULL;
uint32_t mid_len = 0;
int m2mret = OpnMidi_mus2midi(mus, static_cast<uint32_t>(mus_len),
&mid, &mid_len, 0);
if(mus) free(mus);
if(m2mret < 0)
{
errorStringOut = "Invalid MUS/DMX data format!";
return false;
}
cvt_buf.reset(mid);
//Open converted MIDI file
fr.openData(mid, static_cast<size_t>(mid_len));
//Re-Read header again!
goto riffskip;
}
else if(std::memcmp(HeaderBuf, "FORM", 4) == 0)
{
if(std::memcmp(HeaderBuf + 8, "XDIR", 4) != 0)
{
fr.close();
errorStringOut = fr._fileName + ": Invalid format\n";
return false;
}
fr.seek(0, SEEK_END);
size_t mus_len = fr.tell();
fr.seek(0, SEEK_SET);
uint8_t *mus = (uint8_t*)malloc(mus_len);
if(!mus)
{
errorStringOut = "Out of memory!";
return false;
}
fr.read(mus, 1, mus_len);
//Close source stream
fr.close();
uint8_t *mid = NULL;
uint32_t mid_len = 0;
int m2mret = OpnMidi_xmi2midi(mus, static_cast<uint32_t>(mus_len),
&mid, &mid_len, XMIDI_CONVERT_NOCONVERSION);
if(mus) free(mus);
if(m2mret < 0)
{
errorStringOut = "Invalid XMI data format!";
return false;
}
cvt_buf.reset(mid);
//Open converted MIDI file
fr.openData(mid, static_cast<size_t>(mid_len));
//Re-Read header again!
goto riffskip;
}
else
{
// Try to identify RSXX format
if(HeaderBuf[0] == 0x7D)
{
fr.seek(0x6D, SEEK_SET);
fr.read(HeaderBuf, 6, 1);
if(std::memcmp(HeaderBuf, "rsxx}u", 6) == 0)
{
is_RSXX = true;
fr.seek(0x7D, SEEK_SET);
TrackCount = 1;
DeltaTicks = 60;
opn.LogarithmicVolumes = true;
//opl.CartoonersVolumes = true;
opn.m_musicMode = OPN2::MODE_RSXX;
opn.m_volumeScale = OPN2::VOLUME_CMF;
}
}
if(!is_RSXX)
{
if(std::memcmp(HeaderBuf, "MThd\0\0\0\6", 8) != 0)
{
fr.close();
errorStringOut = fr._fileName + ": Invalid format, Header signature is unknown!\n";
return false;
}
/*size_t Fmt = ReadBEint(HeaderBuf + 8, 2);*/
TrackCount = (size_t)ReadBEint(HeaderBuf + 10, 2);
DeltaTicks = (size_t)ReadBEint(HeaderBuf + 12, 2);
}
}
TrackData.clear();
TrackData.resize(TrackCount, std::vector<uint8_t>());
//CurrentPosition.track.clear();
//CurrentPosition.track.resize(TrackCount);
InvDeltaTicks = fraction<uint64_t>(1, 1000000l * static_cast<uint64_t>(DeltaTicks));
//Tempo = 1000000l * InvDeltaTicks;
Tempo = fraction<uint64_t>(1, static_cast<uint64_t>(DeltaTicks));
static const unsigned char EndTag[4] = {0xFF, 0x2F, 0x00, 0x00};
size_t totalGotten = 0;
for(size_t tk = 0; tk < TrackCount; ++tk)
{
// Read track header
size_t TrackLength;
{
if(is_GMF || is_RSXX) // Take the rest of the file
{
size_t pos = fr.tell();
fr.seek(0, SEEK_END);
TrackLength = fr.tell() - pos;
fr.seek(static_cast<long>(pos), SEEK_SET);
}
else
{
fsize = fr.read(HeaderBuf, 1, 8);
if(std::memcmp(HeaderBuf, "MTrk", 4) != 0)
{
fr.close();
errorStringOut = fr._fileName + ": Invalid format, MTrk signature is not found!\n";
return false;
}
TrackLength = (size_t)ReadBEint(HeaderBuf + 4, 4);
}
// Read track data
TrackData[tk].resize(TrackLength);
fsize = fr.read(&TrackData[tk][0], 1, TrackLength);
totalGotten += fsize;
if(is_GMF/*|| is_MUS*/) // Note: CMF does include the track end tag.
TrackData[tk].insert(TrackData[tk].end(), EndTag + 0, EndTag + 4);
if(is_RSXX)//Finalize raw track data with a zero
TrackData[tk].push_back(0);
//bool ok = false;
//// Read next event time
//uint64_t tkDelay = ReadVarLenEx(tk, ok);
//if(ok)
// CurrentPosition.track[tk].delay = tkDelay;
//else
//{
// std::stringstream msg;
// msg << fr._fileName << ": invalid variable length in the track " << tk << "! (error code " << tkDelay << ")";
// OPN2MIDI_ErrorString = msg.str();
// return false;
//}
}
}
for(size_t tk = 0; tk < TrackCount; ++tk)
totalGotten += TrackData[tk].size();
if(totalGotten == 0)
{
errorStringOut = fr._fileName + ": Empty track data";
return false;
}
//Build new MIDI events table (ALPHA!!!)
if(!buildTrackData())
{
errorStringOut = fr._fileName + ": MIDI data parsing error has occouped!\n" + errorString;
return false;
}
opn.Reset(m_setup.PCM_RATE); // Reset AdLib
ch.clear();
ch.resize(opn.NumChannels);
return true;
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,451 @@
/*
* MUS2MIDI: MUS to MIDI Library
*
* Copyright (C) 2014 Bret Curtis
* Copyright (C) WildMIDI Developers 2015-2016
* ADLMIDI Library API: Copyright (c) 2017-2018 Vitaly Novichkov <admin@wohlnet.ru>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public
* License along with this library; if not, write to the
* Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#include <stddef.h>
#include <stdlib.h>
#include <string.h>
#include "opnmidi_mus2mid.h"
#define FREQUENCY 140 /* default Hz or BPM */
#if 0 /* older units: */
#define TEMPO 0x001aa309 /* MPQN: 60000000 / 34.37Hz = 1745673 */
#define DIVISION 0x0059 /* 89 -- used by many mus2midi converters */
#endif
#define TEMPO 0x00068A1B /* MPQN: 60000000 / 140BPM (140Hz) = 428571 */
/* 0x000D1436 -> MPQN: 60000000 / 70BPM (70Hz) = 857142 */
#define DIVISION 0x0101 /* 257 for 140Hz files with a 140MPQN */
/* 0x0088 -> 136 for 70Hz files with a 140MPQN */
/* 0x010B -> 267 for 70hz files with a 70MPQN */
/* 0x01F9 -> 505 for 140hz files with a 70MPQN */
/* New
* QLS: MPQN/1000000 = 0.428571
* TDPS: QLS/PPQN = 0.428571/136 = 0.003151257
* PPQN: 136
*
* QLS: MPQN/1000000 = 0.428571
* TDPS: QLS/PPQN = 0.428571/257 = 0.001667591
* PPQN: 257
*
* QLS: MPQN/1000000 = 0.857142
* TDPS: QLS/PPQN = 0.857142/267 = 0.00321027
* PPQN: 267
*
* QLS: MPQN/1000000 = 0.857142
* TDPS: QLS/PPQN = 0.857142/505 = 0.001697311
* PPQN: 505
*
* Old
* QLS: MPQN/1000000 = 1.745673
* TDPS: QLS/PPQN = 1.745673 / 89 = 0.019614303 (seconds per tick)
* PPQN: (TDPS = QLS/PPQN) (0.019614303 = 1.745673/PPQN) (0.019614303*PPQN = 1.745673) (PPQN = 89.000001682)
*
*/
#define MUSEVENT_KEYOFF 0
#define MUSEVENT_KEYON 1
#define MUSEVENT_PITCHWHEEL 2
#define MUSEVENT_CHANNELMODE 3
#define MUSEVENT_CONTROLLERCHANGE 4
#define MUSEVENT_END 6
#define MIDI_MAXCHANNELS 16
static char MUS_ID[] = { 'M', 'U', 'S', 0x1A };
static uint8_t midimap[] =
{/* MIDI Number Description */
0, /* 0 program change */
0, /* 1 bank selection */
0x01, /* 2 Modulation pot (frequency vibrato depth) */
0x07, /* 3 Volume: 0-silent, ~100-normal, 127-loud */
0x0A, /* 4 Pan (balance) pot: 0-left, 64-center (default), 127-right */
0x0B, /* 5 Expression pot */
0x5B, /* 6 Reverb depth */
0x5D, /* 7 Chorus depth */
0x40, /* 8 Sustain pedal */
0x43, /* 9 Soft pedal */
0x78, /* 10 All sounds off */
0x7B, /* 11 All notes off */
0x7E, /* 12 Mono (use numchannels + 1) */
0x7F, /* 13 Poly */
0x79, /* 14 reset all controllers */
};
typedef struct MUSHeader {
char ID[4]; /* identifier: "MUS" 0x1A */
uint16_t scoreLen;
uint16_t scoreStart;
uint16_t channels; /* count of primary channels */
uint16_t sec_channels; /* count of secondary channels */
uint16_t instrCnt;
} MUSHeader ;
#define MUS_HEADERSIZE 14
typedef struct MidiHeaderChunk {
char name[4];
int32_t length;
int16_t format; /* make 0 */
int16_t ntracks;/* make 1 */
int16_t division; /* 0xe250 ?? */
} MidiHeaderChunk;
#define MIDI_HEADERSIZE 14
typedef struct MidiTrackChunk {
char name[4];
int32_t length;
} MidiTrackChunk;
#define TRK_CHUNKSIZE 8
struct mus_ctx {
uint8_t *src, *src_ptr;
uint32_t srcsize;
uint32_t datastart;
uint8_t *dst, *dst_ptr;
uint32_t dstsize, dstrem;
};
#define DST_CHUNK 8192
static void resize_dst(struct mus_ctx *ctx) {
uint32_t pos = (uint32_t)(ctx->dst_ptr - ctx->dst);
ctx->dst = realloc(ctx->dst, ctx->dstsize + DST_CHUNK);
ctx->dstsize += DST_CHUNK;
ctx->dstrem += DST_CHUNK;
ctx->dst_ptr = ctx->dst + pos;
}
static void write1(struct mus_ctx *ctx, uint32_t val)
{
if (ctx->dstrem < 1)
resize_dst(ctx);
*ctx->dst_ptr++ = val & 0xff;
ctx->dstrem--;
}
static void write2(struct mus_ctx *ctx, uint32_t val)
{
if (ctx->dstrem < 2)
resize_dst(ctx);
*ctx->dst_ptr++ = (val>>8) & 0xff;
*ctx->dst_ptr++ = val & 0xff;
ctx->dstrem -= 2;
}
static void write4(struct mus_ctx *ctx, uint32_t val)
{
if (ctx->dstrem < 4)
resize_dst(ctx);
*ctx->dst_ptr++ = (val>>24)&0xff;
*ctx->dst_ptr++ = (val>>16)&0xff;
*ctx->dst_ptr++ = (val>>8) & 0xff;
*ctx->dst_ptr++ = val & 0xff;
ctx->dstrem -= 4;
}
static void seekdst(struct mus_ctx *ctx, uint32_t pos) {
ctx->dst_ptr = ctx->dst + pos;
while (ctx->dstsize < pos)
resize_dst(ctx);
ctx->dstrem = ctx->dstsize - pos;
}
static void skipdst(struct mus_ctx *ctx, int32_t pos) {
size_t newpos;
ctx->dst_ptr += pos;
newpos = ctx->dst_ptr - ctx->dst;
while (ctx->dstsize < newpos)
resize_dst(ctx);
ctx->dstrem = (uint32_t)(ctx->dstsize - newpos);
}
static uint32_t getdstpos(struct mus_ctx *ctx) {
return (uint32_t)(ctx->dst_ptr - ctx->dst);
}
/* writes a variable length integer to a buffer, and returns bytes written */
static int32_t writevarlen(int32_t value, uint8_t *out)
{
int32_t buffer, count = 0;
buffer = value & 0x7f;
while ((value >>= 7) > 0) {
buffer <<= 8;
buffer += 0x80;
buffer += (value & 0x7f);
}
while (1) {
++count;
*out = (uint8_t)buffer;
++out;
if (buffer & 0x80)
buffer >>= 8;
else
break;
}
return (count);
}
#define READ_INT16(b) ((b)[0] | ((b)[1] << 8))
#define READ_INT32(b) ((b)[0] | ((b)[1] << 8) | ((b)[2] << 16) | ((b)[3] << 24))
int OpnMidi_mus2midi(uint8_t *in, uint32_t insize,
uint8_t **out, uint32_t *outsize,
uint16_t frequency) {
struct mus_ctx ctx;
MUSHeader header;
uint8_t *cur, *end;
uint32_t track_size_pos, begin_track_pos, current_pos;
int32_t delta_time;/* Delta time for midi event */
int temp, ret = -1;
int channel_volume[MIDI_MAXCHANNELS];
int channelMap[MIDI_MAXCHANNELS], currentChannel;
if (insize < MUS_HEADERSIZE) {
/*_WM_GLOBAL_ERROR(__FUNCTION__, __LINE__, WM_ERR_CORUPT, "(too short)", 0);*/
return (-1);
}
if (!frequency)
frequency = FREQUENCY;
/* read the MUS header and set our location */
memcpy(header.ID, in, 4);
header.scoreLen = READ_INT16(&in[4]);
header.scoreStart = READ_INT16(&in[6]);
header.channels = READ_INT16(&in[8]);
header.sec_channels = READ_INT16(&in[10]);
header.instrCnt = READ_INT16(&in[12]);
if (memcmp(header.ID, MUS_ID, 4)) {
/*_WM_GLOBAL_ERROR(__FUNCTION__, __LINE__, WM_ERR_NOT_MUS, NULL, 0);*/
return (-1);
}
if (insize < (uint32_t)header.scoreLen + (uint32_t)header.scoreStart) {
/*_WM_GLOBAL_ERROR(__FUNCTION__, __LINE__, WM_ERR_CORUPT, "(too short)", 0);*/
return (-1);
}
/* channel #15 should be excluded in the numchannels field: */
if (header.channels > MIDI_MAXCHANNELS - 1) {
/*_WM_GLOBAL_ERROR(__FUNCTION__, __LINE__, WM_ERR_INVALID, NULL, 0);*/
return (-1);
}
memset(&ctx, 0, sizeof(struct mus_ctx));
ctx.src = ctx.src_ptr = in;
ctx.srcsize = insize;
ctx.dst = calloc(DST_CHUNK, sizeof(uint8_t));
ctx.dst_ptr = ctx.dst;
ctx.dstsize = DST_CHUNK;
ctx.dstrem = DST_CHUNK;
/* Map channel 15 to 9 (percussions) */
for (temp = 0; temp < MIDI_MAXCHANNELS; ++temp) {
channelMap[temp] = -1;
channel_volume[temp] = 0x40;
}
channelMap[15] = 9;
/* Header is 14 bytes long and add the rest as well */
write1(&ctx, 'M');
write1(&ctx, 'T');
write1(&ctx, 'h');
write1(&ctx, 'd');
write4(&ctx, 6); /* length of header */
write2(&ctx, 0); /* MIDI type (always 0) */
write2(&ctx, 1); /* MUS files only have 1 track */
write2(&ctx, DIVISION); /* division */
/* Write out track header and track length position for later */
begin_track_pos = getdstpos(&ctx);
write1(&ctx, 'M');
write1(&ctx, 'T');
write1(&ctx, 'r');
write1(&ctx, 'k');
track_size_pos = getdstpos(&ctx);
skipdst(&ctx, 4);
/* write tempo: microseconds per quarter note */
write1(&ctx, 0x00); /* delta time */
write1(&ctx, 0xff); /* sys command */
write2(&ctx, 0x5103); /* command - set tempo */
write1(&ctx, TEMPO & 0x000000ff);
write1(&ctx, (TEMPO & 0x0000ff00) >> 8);
write1(&ctx, (TEMPO & 0x00ff0000) >> 16);
/* Percussions channel starts out at full volume */
write1(&ctx, 0x00);
write1(&ctx, 0xB9);
write1(&ctx, 0x07);
write1(&ctx, 127);
/* get current position in source, and end of position */
cur = in + header.scoreStart;
end = cur + header.scoreLen;
currentChannel = 0;
delta_time = 0;
/* main loop */
while(cur < end){
/*printf("LOOP DEBUG: %d\r\n",iterator++);*/
uint8_t channel;
uint8_t event;
uint8_t temp_buffer[32]; /* temp buffer for current iterator */
uint8_t *out_local = temp_buffer;
uint8_t status, bit1, bit2, bitc = 2;
/* read in current bit */
event = *cur++;
channel = (event & 15); /* current channel */
/* write variable length delta time */
out_local += writevarlen(delta_time, out_local);
/* set all channels to 127 (max) volume */
if (channelMap[channel] < 0) {
*out_local++ = 0xB0 + currentChannel;
*out_local++ = 0x07;
*out_local++ = 127;
*out_local++ = 0x00;
channelMap[channel] = currentChannel++;
if (currentChannel == 9)
++currentChannel;
}
status = channelMap[channel];
/* handle events */
switch ((event & 122) >> 4){
case MUSEVENT_KEYOFF:
status |= 0x80;
bit1 = *cur++;
bit2 = 0x40;
break;
case MUSEVENT_KEYON:
status |= 0x90;
bit1 = *cur & 127;
if (*cur++ & 128) /* volume bit? */
channel_volume[channelMap[channel]] = *cur++;
bit2 = channel_volume[channelMap[channel]];
break;
case MUSEVENT_PITCHWHEEL:
status |= 0xE0;
bit1 = (*cur & 1) >> 6;
bit2 = (*cur++ >> 1) & 127;
break;
case MUSEVENT_CHANNELMODE:
status |= 0xB0;
if (*cur >= sizeof(midimap) / sizeof(midimap[0])) {
/*_WM_ERROR_NEW("%s:%i: can't map %u to midi",
__FUNCTION__, __LINE__, *cur);*/
goto _end;
}
bit1 = midimap[*cur++];
bit2 = (*cur++ == 12) ? header.channels + 1 : 0x00;
break;
case MUSEVENT_CONTROLLERCHANGE:
if (*cur == 0) {
cur++;
status |= 0xC0;
bit1 = *cur++;
bit2 = 0;/* silence bogus warnings */
bitc = 1;
} else {
status |= 0xB0;
if (*cur >= sizeof(midimap) / sizeof(midimap[0])) {
/*_WM_ERROR_NEW("%s:%i: can't map %u to midi",
__FUNCTION__, __LINE__, *cur);*/
goto _end;
}
bit1 = midimap[*cur++];
bit2 = *cur++;
}
break;
case MUSEVENT_END: /* End */
status = 0xff;
bit1 = 0x2f;
bit2 = 0x00;
if (cur != end) { /* should we error here or report-only? */
/*_WM_DEBUG_MSG("%s:%i: MUS buffer off by %ld bytes",
__FUNCTION__, __LINE__, (long)(cur - end));*/
}
break;
case 5:/* Unknown */
case 7:/* Unknown */
default:/* shouldn't happen */
/*_WM_ERROR_NEW("%s:%i: unrecognized event (%u)",
__FUNCTION__, __LINE__, event);*/
goto _end;
}
/* write it out */
*out_local++ = status;
*out_local++ = bit1;
if (bitc == 2)
*out_local++ = bit2;
/* write out our temp buffer */
if (out_local != temp_buffer)
{
if (ctx.dstrem < sizeof(temp_buffer))
resize_dst(&ctx);
memcpy(ctx.dst_ptr, temp_buffer, out_local - temp_buffer);
ctx.dst_ptr += out_local - temp_buffer;
ctx.dstrem -= out_local - temp_buffer;
}
if (event & 128) {
delta_time = 0;
do {
delta_time = (int32_t)((delta_time * 128 + (*cur & 127)) * (140.0 / (double)frequency));
} while ((*cur++ & 128));
} else {
delta_time = 0;
}
}
/* write out track length */
current_pos = getdstpos(&ctx);
seekdst(&ctx, track_size_pos);
write4(&ctx, current_pos - begin_track_pos - TRK_CHUNKSIZE);
seekdst(&ctx, current_pos); /* reseek to end position */
*out = ctx.dst;
*outsize = ctx.dstsize - ctx.dstrem;
ret = 0;
_end: /* cleanup */
if (ret < 0) {
free(ctx.dst);
*out = NULL;
*outsize = 0;
}
return (ret);
}

View file

@ -0,0 +1,49 @@
/*
* MUS2MIDI: DMX (DOOM) MUS to MIDI Library Header
*
* Copyright (C) 2014-2016 Bret Curtis
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public
* License along with this library; if not, write to the
* Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#ifndef MUSLIB_H
#define MUSLIB_H
#include <stdint.h>
#ifdef __DJGPP__
typedef signed char int8_t;
typedef unsigned char uint8_t;
typedef signed short int16_t;
typedef unsigned short uint16_t;
typedef signed long int32_t;
typedef unsigned long uint32_t;
#endif
#ifdef __cplusplus
extern "C"
{
#endif
int OpnMidi_mus2midi(uint8_t *in, uint32_t insize,
uint8_t **out, uint32_t *outsize,
uint16_t frequency);
#ifdef __cplusplus
}
#endif
#endif /* MUSLIB_H */

View file

@ -0,0 +1,318 @@
/*
* libOPNMIDI is a free MIDI to WAV conversion library with OPN2 (YM2612) emulation
*
* MIDI parser and player (Original code from ADLMIDI): Copyright (c) 2010-2014 Joel Yliluoma <bisqwit@iki.fi>
* OPNMIDI Library and YM2612 support: Copyright (c) 2017-2018 Vitaly Novichkov <admin@wohlnet.ru>
*
* Library is based on the ADLMIDI, a MIDI player for Linux and Windows with OPL3 emulation:
* http://iki.fi/bisqwit/source/adlmidi.html
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "opnmidi_private.hpp"
static const uint8_t NoteChannels[6] = { 0, 1, 2, 4, 5, 6 };
static inline void getOpnChannel(uint32_t in_channel,
unsigned &out_card,
uint8_t &out_port,
uint8_t &out_ch)
{
out_card = in_channel / 6;
uint8_t ch4 = in_channel % 6;
out_port = ((ch4 < 3) ? 0 : 1);
out_ch = ch4 % 3;
}
const opnInstMeta &OPN2::GetAdlMetaIns(size_t n)
{
return dynamic_metainstruments[n];
}
size_t OPN2::GetAdlMetaNumber(size_t midiins)
{
return midiins;
}
static const opnInstData opn2_emptyInstrument = {
{
{{0, 0, 0, 0, 0, 0, 0}},
{{0, 0, 0, 0, 0, 0, 0}},
{{0, 0, 0, 0, 0, 0, 0}},
{{0, 0, 0, 0, 0, 0, 0}}
},
0, 0, 0
};
const opnInstData &OPN2::GetAdlIns(size_t insno)
{
if(insno >= dynamic_instruments.size())
return opn2_emptyInstrument;
return dynamic_instruments[insno];
}
OPN2::OPN2() :
regLFO(0),
dynamic_percussion_offset(128),
DynamicInstrumentTag(0x8000u),
DynamicMetaInstrumentTag(0x4000000u),
NumCards(1),
LogarithmicVolumes(false),
m_musicMode(MODE_MIDI),
m_volumeScale(VOLUME_Generic)
{}
OPN2::~OPN2()
{
ClearChips();
}
void OPN2::PokeO(size_t card, uint8_t port, uint8_t index, uint8_t value)
{
#ifdef USE_LEGACY_EMULATOR
if(port == 1)
cardsOP2[card]->write1(index, value);
else
cardsOP2[card]->write0(index, value);
#else
OPN2_WriteBuffered(cardsOP2[card], 0 + (port) * 2, index);
OPN2_WriteBuffered(cardsOP2[card], 1 + (port) * 2, value);
#endif
}
void OPN2::NoteOff(size_t c)
{
unsigned card;
uint8_t port, cc;
uint8_t ch4 = c % 6;
getOpnChannel(uint16_t(c), card, port, cc);
PokeO(card, 0, 0x28, NoteChannels[ch4]);
}
void OPN2::NoteOn(unsigned c, double hertz) // Hertz range: 0..131071
{
unsigned card;
uint8_t port, cc;
uint8_t ch4 = c % 6;
getOpnChannel(uint16_t(c), card, port, cc);
uint16_t x2 = 0x0000;
if(hertz < 0 || hertz > 262143) // Avoid infinite loop
return;
while(hertz >= 2047.5)
{
hertz /= 2.0; // Calculate octave
x2 += 0x800;
}
x2 += static_cast<uint32_t>(hertz + 0.5);
PokeO(card, port, 0xA4 + cc, (x2>>8) & 0xFF);//Set frequency and octave
PokeO(card, port, 0xA0 + cc, x2 & 0xFF);
PokeO(card, 0, 0x28, 0xF0 + NoteChannels[ch4]);
pit[c] = static_cast<uint8_t>(x2 >> 8);
}
void OPN2::Touch_Real(unsigned c, unsigned volume, uint8_t brightness)
{
if(volume > 127) volume = 127;
unsigned card;
uint8_t port, cc;
getOpnChannel(c, card, port, cc);
size_t i = ins[c];
const opnInstData &adli = GetAdlIns(i);
uint8_t op_vol[4] =
{
adli.OPS[OPERATOR1].data[1],
adli.OPS[OPERATOR2].data[1],
adli.OPS[OPERATOR3].data[1],
adli.OPS[OPERATOR4].data[1],
};
bool alg_do[8][4] =
{
/*
* Yeah, Operator 2 and 3 are seems swapped
* which we can see in the algorithm 4
*/
//OP1 OP3 OP2 OP4
//30 34 38 3C
{false,false,false,true},//Algorithm #0: W = 1 * 2 * 3 * 4
{false,false,false,true},//Algorithm #1: W = (1 + 2) * 3 * 4
{false,false,false,true},//Algorithm #2: W = (1 + (2 * 3)) * 4
{false,false,false,true},//Algorithm #3: W = ((1 * 2) + 3) * 4
{false,false,true, true},//Algorithm #4: W = (1 * 2) + (3 * 4)
{false,true ,true ,true},//Algorithm #5: W = (1 * (2 + 3 + 4)
{false,true ,true ,true},//Algorithm #6: W = (1 * 2) + 3 + 4
{true ,true ,true ,true},//Algorithm #7: W = 1 + 2 + 3 + 4
};
uint8_t alg = adli.fbalg & 0x07;
for(uint8_t op = 0; op < 4; op++)
{
bool do_op = alg_do[alg][op] || ScaleModulators;
uint8_t x = op_vol[op];
uint8_t vol_res = do_op ? uint8_t(127 - (volume * (127 - (x&127)))/127) : x;
if(brightness != 127)
{
brightness = static_cast<uint8_t>(::round(127.0 * ::sqrt((static_cast<double>(brightness)) * (1.0 / 127.0))));
if(!do_op)
vol_res = uint8_t(127 - (brightness * (127 - (uint32_t(vol_res) & 127))) / 127);
}
PokeO(card, port, 0x40 + cc + (4 * op), vol_res);
}
// Correct formula (ST3, AdPlug):
// 63-((63-(instrvol))/63)*chanvol
// Reduces to (tested identical):
// 63 - chanvol + chanvol*instrvol/63
// Also (slower, floats):
// 63 + chanvol * (instrvol / 63.0 - 1)
}
void OPN2::Patch(uint16_t c, size_t i)
{
unsigned card;
uint8_t port, cc;
getOpnChannel(uint16_t(c), card, port, cc);
ins[c] = i;
const opnInstData &adli = GetAdlIns(i);
#if 1 //Reg1-Op1, Reg1-Op2, Reg1-Op3, Reg1-Op4,....
for(uint8_t d = 0; d < 7; d++)
{
for(uint8_t op = 0; op < 4; op++)
PokeO(card, port, 0x30 + (0x10 * d) + (op * 4) + cc, adli.OPS[op].data[d]);
}
#else //Reg1-Op1, Reg2-Op1, Reg3-Op1, Reg4-Op1,....
for(uint8_t op = 0; op < 4; op++)
{
PokeO(card, port, 0x30 + (op * 4) + cc, adli.OPS[op].data[0]);
PokeO(card, port, 0x40 + (op * 4) + cc, adli.OPS[op].data[1]);
PokeO(card, port, 0x50 + (op * 4) + cc, adli.OPS[op].data[2]);
PokeO(card, port, 0x60 + (op * 4) + cc, adli.OPS[op].data[3]);
PokeO(card, port, 0x70 + (op * 4) + cc, adli.OPS[op].data[4]);
PokeO(card, port, 0x80 + (op * 4) + cc, adli.OPS[op].data[5]);
PokeO(card, port, 0x90 + (op * 4) + cc, adli.OPS[op].data[6]);
}
#endif
PokeO(card, port, 0xB0 + cc, adli.fbalg);//Feedback/Algorithm
regBD[c] = (regBD[c] & 0xC0) | (adli.lfosens & 0x3F);
PokeO(card, port, 0xB4 + cc, regBD[c]);//Panorame and LFO bits
}
void OPN2::Pan(unsigned c, unsigned value)
{
unsigned card;
uint8_t port, cc;
getOpnChannel(uint16_t(c), card, port, cc);
const opnInstData &adli = GetAdlIns(ins[c]);
uint8_t val = (value & 0xC0) | (adli.lfosens & 0x3F);
regBD[c] = val;
PokeO(card, port, 0xB4 + cc, val);
}
void OPN2::Silence() // Silence all OPL channels.
{
for(unsigned c = 0; c < NumChannels; ++c)
{
NoteOff(c);
Touch_Real(c, 0);
}
}
void OPN2::ChangeVolumeRangesModel(OPNMIDI_VolumeModels volumeModel)
{
switch(volumeModel)
{
case OPNMIDI_VolumeModel_AUTO://Do nothing until restart playing
break;
case OPNMIDI_VolumeModel_Generic:
m_volumeScale = OPN2::VOLUME_Generic;
break;
case OPNMIDI_VolumeModel_CMF:
LogarithmicVolumes = true;
m_volumeScale = OPN2::VOLUME_CMF;
break;
case OPNMIDI_VolumeModel_DMX:
m_volumeScale = OPN2::VOLUME_DMX;
break;
case OPNMIDI_VolumeModel_APOGEE:
m_volumeScale = OPN2::VOLUME_APOGEE;
break;
case OPNMIDI_VolumeModel_9X:
m_volumeScale = OPN2::VOLUME_9X;
break;
}
}
void OPN2::ClearChips()
{
for(size_t i = 0; i < cardsOP2.size(); i++)
delete cardsOP2[i];
cardsOP2.clear();
}
void OPN2::Reset(unsigned long PCM_RATE)
{
ClearChips();
ins.clear();
pit.clear();
regBD.clear();
cardsOP2.resize(NumCards, NULL);
#ifndef USE_LEGACY_EMULATOR
OPN2_SetChipType(ym3438_type_asic);
#endif
for(size_t i = 0; i < cardsOP2.size(); i++)
{
#ifdef USE_LEGACY_EMULATOR
cardsOP2[i] = new OPNMIDI_Ym2612_Emu();
cardsOP2[i]->set_rate(PCM_RATE, 7670454.0);
#else
cardsOP2[i] = new ym3438_t;
std::memset(cardsOP2[i], 0, sizeof(ym3438_t));
OPN2_Reset(cardsOP2[i], (Bit32u)PCM_RATE, 7670454);
#endif
}
NumChannels = NumCards * 6;
ins.resize(NumChannels, 189);
pit.resize(NumChannels, 0);
regBD.resize(NumChannels, 0);
for(unsigned card = 0; card < NumCards; ++card)
{
PokeO(card, 0, 0x22, regLFO);//push current LFO state
PokeO(card, 0, 0x27, 0x00); //set Channel 3 normal mode
PokeO(card, 0, 0x2B, 0x00); //Disable DAC
//Shut up all channels
PokeO(card, 0, 0x28, 0x00 ); //Note Off 0 channel
PokeO(card, 0, 0x28, 0x01 ); //Note Off 1 channel
PokeO(card, 0, 0x28, 0x02 ); //Note Off 2 channel
PokeO(card, 0, 0x28, 0x04 ); //Note Off 3 channel
PokeO(card, 0, 0x28, 0x05 ); //Note Off 4 channel
PokeO(card, 0, 0x28, 0x06 ); //Note Off 5 channel
}
Silence();
}

View file

@ -0,0 +1,55 @@
/*
* libOPNMIDI is a free MIDI to WAV conversion library with OPN2 (YM2612) emulation
*
* MIDI parser and player (Original code from ADLMIDI): Copyright (c) 2010-2014 Joel Yliluoma <bisqwit@iki.fi>
* OPNMIDI Library and YM2612 support: Copyright (c) 2017-2018 Vitaly Novichkov <admin@wohlnet.ru>
*
* Library is based on the ADLMIDI, a MIDI player for Linux and Windows with OPL3 emulation:
* http://iki.fi/bisqwit/source/adlmidi.html
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "opnmidi_private.hpp"
std::string OPN2MIDI_ErrorString;
int opn2RefreshNumCards(OPN2_MIDIPlayer * /*device*/)
{
// OPNMIDIplay *play = reinterpret_cast<OPNMIDIplay *>(device->opn2_midiPlayer);
//OPN uses 4-op instruments only
// unsigned n_fourop[2] = {0, 0}, n_total[2] = {0, 0};
// for(unsigned a = 0; a < 256; ++a)
// {
// unsigned insno = banks[device->OpnBank][a];
// if(insno == 198) continue;
// ++n_total[a / 128];
// if(adlins[insno].adlno1 != adlins[insno].adlno2)
// ++n_fourop[a / 128];
// }
// device->NumFourOps =
// (n_fourop[0] >= n_total[0] * 7 / 8) ? device->NumCards * 6
// : (n_fourop[0] < n_total[0] * 1 / 8) ? 0
// : (device->NumCards == 1 ? 1 : device->NumCards * 4);
// reinterpret_cast<OPNMIDIplay *>(device->opn2_midiPlayer)->opn.NumFourOps = device->NumFourOps;
// if(n_fourop[0] >= n_total[0] * 15 / 16 && device->NumFourOps == 0)
// {
// OPN2MIDI_ErrorString = "ERROR: You have selected a bank that consists almost exclusively of four-op patches.\n"
// " The results (silence + much cpu load) would be probably\n"
// " not what you want, therefore ignoring the request.\n";
// return -1;
// }
return 0;
}

View file

@ -0,0 +1,894 @@
/*
* libADLMIDI is a free MIDI to WAV conversion library with OPL3 emulation
*
* Original ADLMIDI code: Copyright (c) 2010-2014 Joel Yliluoma <bisqwit@iki.fi>
* ADLMIDI Library API: Copyright (c) 2017-2018 Vitaly Novichkov <admin@wohlnet.ru>
*
* Library is based on the ADLMIDI, a MIDI player for Linux and Windows with OPL3 emulation:
* http://iki.fi/bisqwit/source/adlmidi.html
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef ADLMIDI_PRIVATE_HPP
#define ADLMIDI_PRIVATE_HPP
// Setup compiler defines useful for exporting required public API symbols in gme.cpp
#ifndef OPNMIDI_EXPORT
# if defined (_WIN32) && defined(ADLMIDI_BUILD_DLL)
# define OPNMIDI_EXPORT __declspec(dllexport)
# elif defined (LIBADLMIDI_VISIBILITY)
# define OPNMIDI_EXPORT __attribute__((visibility ("default")))
# else
# define OPNMIDI_EXPORT
# endif
#endif
#ifdef _WIN32
# undef NO_OLDNAMES
# ifdef _MSC_VER
# ifdef _WIN64
typedef __int64 ssize_t;
# else
typedef __int32 ssize_t;
# endif
# define NOMINMAX //Don't override std::min and std::max
# endif
# include <windows.h>
#endif
#include <vector>
#include <list>
#include <string>
#include <sstream>
#include <map>
#include <set>
#include <cstdlib>
#include <cstring>
#include <cmath>
#include <cstdarg>
#include <cstdio>
#include <vector> // vector
#include <deque> // deque
#include <cmath> // exp, log, ceil
#include <stdio.h>
#include <stdlib.h>
#include <limits> // numeric_limit
#ifndef _WIN32
#include <errno.h>
#endif
#include <deque>
#include <algorithm>
#include "fraction.hpp"
#ifdef USE_LEGACY_EMULATOR
#include "Ym2612_ChipEmu.h"
#else
#include "ym3438.h"
#endif
#include "opnbank.h"
#include "opnmidi.h"
#define ADL_UNUSED(x) (void)x
extern std::string OPN2MIDI_ErrorString;
/*
Smart pointer for C heaps, created with malloc() call.
FAQ: Why not std::shared_ptr? Because of Android NDK now doesn't supports it
*/
template<class PTR>
class AdlMIDI_CPtr
{
PTR *m_p;
public:
AdlMIDI_CPtr() : m_p(NULL) {}
~AdlMIDI_CPtr()
{
reset(NULL);
}
void reset(PTR *p = NULL)
{
if(m_p)
free(m_p);
m_p = p;
}
PTR *get()
{
return m_p;
}
PTR &operator*()
{
return *m_p;
}
PTR *operator->()
{
return m_p;
}
};
class OPNMIDIplay;
class OPN2
{
public:
friend class OPNMIDIplay;
uint32_t NumChannels;
char ____padding[4];
#ifdef USE_LEGACY_EMULATOR
std::vector<OPNMIDI_Ym2612_Emu*> cardsOP2;
#else
std::vector<ym3438_t*> cardsOP2;
#endif
private:
std::vector<size_t> ins; // index to adl[], cached, needed by Touch()
std::vector<uint8_t> pit; // value poked to B0, cached, needed by NoteOff)(
std::vector<uint8_t> regBD;
uint8_t regLFO;
//! Meta information about every instrument
std::vector<opnInstMeta> dynamic_metainstruments;
//! Raw instrument data ready to be sent to the chip
std::vector<opnInstData> dynamic_instruments;
size_t dynamic_percussion_offset;
typedef std::map<uint16_t, size_t> BankMap;
BankMap dynamic_melodic_banks;
BankMap dynamic_percussion_banks;
const unsigned DynamicInstrumentTag /* = 0x8000u*/,
DynamicMetaInstrumentTag /* = 0x4000000u*/;
const opnInstMeta &GetAdlMetaIns(size_t n);
size_t GetAdlMetaNumber(size_t midiins);
const opnInstData &GetAdlIns(size_t insno);
public:
//! Total number of running concurrent emulated chips
unsigned int NumCards;
//! Carriers-only are scaled by default by volume level. This flag will tell to scale modulators too.
bool ScaleModulators;
//! Required to play CMF files. Can be turned on by using of "CMF" volume model
bool LogarithmicVolumes;
char ___padding2[3];
enum MusicMode
{
MODE_MIDI,
//MODE_IMF, OPN2 chip is not able to interpret OPL's except of a creepy and ugly conversion :-P
//MODE_CMF, CMF also is not supported :-P
MODE_RSXX
} m_musicMode;
enum VolumesScale
{
VOLUME_Generic,
VOLUME_CMF,
VOLUME_DMX,
VOLUME_APOGEE,
VOLUME_9X
} m_volumeScale;
OPN2();
~OPN2();
char ____padding3[8];
std::vector<char> four_op_category; // 1 = quad-master, 2 = quad-slave, 0 = regular
// 3 = percussion BassDrum
// 4 = percussion Snare
// 5 = percussion Tom
// 6 = percussion Crash cymbal
// 7 = percussion Hihat
// 8 = percussion slave
void PokeO(size_t card, uint8_t port, uint8_t index, uint8_t value);
void NoteOff(size_t c);
void NoteOn(unsigned c, double hertz);
void Touch_Real(unsigned c, unsigned volume, uint8_t brightness = 127);
void Patch(uint16_t c, size_t i);
void Pan(unsigned c, unsigned value);
void Silence();
void ChangeVolumeRangesModel(OPNMIDI_VolumeModels volumeModel);
void ClearChips();
void Reset(unsigned long PCM_RATE);
};
/**
* @brief Hooks of the internal events
*/
struct MIDIEventHooks
{
MIDIEventHooks() :
onEvent(NULL),
onEvent_userData(NULL),
onNote(NULL),
onNote_userData(NULL),
onDebugMessage(NULL),
onDebugMessage_userData(NULL)
{}
//! Raw MIDI event hook
typedef void (*RawEventHook)(void *userdata, uint8_t type, uint8_t subtype, uint8_t channel, const uint8_t *data, size_t len);
RawEventHook onEvent;
void *onEvent_userData;
//! Note on/off hooks
typedef void (*NoteHook)(void *userdata, int adlchn, int note, int ins, int pressure, double bend);
NoteHook onNote;
void *onNote_userData;
//! Library internal debug messages
typedef void (*DebugMessageHook)(void *userdata, const char *fmt, ...);
DebugMessageHook onDebugMessage;
void *onDebugMessage_userData;
};
class OPNMIDIplay
{
friend void opn2_reset(struct OPN2_MIDIPlayer*);
public:
OPNMIDIplay(unsigned long sampleRate = 22050);
~OPNMIDIplay()
{}
void applySetup();
/**********************Internal structures and classes**********************/
/**
* @brief A little class gives able to read filedata from disk and also from a memory segment
*/
class fileReader
{
public:
enum relTo
{
SET = 0,
CUR = 1,
END = 2
};
fileReader()
{
fp = NULL;
mp = NULL;
mp_size = 0;
mp_tell = 0;
}
~fileReader()
{
close();
}
void openFile(const char *path)
{
#ifndef _WIN32
fp = std::fopen(path, "rb");
#else
wchar_t widePath[MAX_PATH];
int size = MultiByteToWideChar(CP_UTF8, 0, path, (int)std::strlen(path), widePath, MAX_PATH);
widePath[size] = '\0';
fp = _wfopen(widePath, L"rb");
#endif
_fileName = path;
mp = NULL;
mp_size = 0;
mp_tell = 0;
}
void openData(const void *mem, size_t lenght)
{
fp = NULL;
mp = mem;
mp_size = lenght;
mp_tell = 0;
}
void seek(long pos, int rel_to)
{
if(fp)
std::fseek(fp, pos, rel_to);
else
{
switch(rel_to)
{
case SET:
mp_tell = static_cast<size_t>(pos);
break;
case END:
mp_tell = mp_size - static_cast<size_t>(pos);
break;
case CUR:
mp_tell = mp_tell + static_cast<size_t>(pos);
break;
}
if(mp_tell > mp_size)
mp_tell = mp_size;
}
}
inline void seeku(uint64_t pos, int rel_to)
{
seek(static_cast<long>(pos), rel_to);
}
size_t read(void *buf, size_t num, size_t size)
{
if(fp)
return std::fread(buf, num, size, fp);
else
{
size_t pos = 0;
size_t maxSize = static_cast<size_t>(size * num);
while((pos < maxSize) && (mp_tell < mp_size))
{
reinterpret_cast<unsigned char *>(buf)[pos] = reinterpret_cast<unsigned const char *>(mp)[mp_tell];
mp_tell++;
pos++;
}
return pos;
}
}
int getc()
{
if(fp)
return std::getc(fp);
else
{
if(mp_tell >= mp_size) return -1;
int x = reinterpret_cast<unsigned const char *>(mp)[mp_tell];
mp_tell++;
return x;
}
}
size_t tell()
{
if(fp)
return static_cast<size_t>(std::ftell(fp));
else
return mp_tell;
}
void close()
{
if(fp) std::fclose(fp);
fp = NULL;
mp = NULL;
mp_size = 0;
mp_tell = 0;
}
bool isValid()
{
return (fp) || (mp);
}
bool eof()
{
if(fp)
return std::feof(fp);
else
return mp_tell >= mp_size;
}
std::string _fileName;
std::FILE *fp;
const void *mp;
size_t mp_size;
size_t mp_tell;
};
// Persistent settings for each MIDI channel
struct MIDIchannel
{
uint16_t portamento;
uint8_t bank_lsb, bank_msb;
uint8_t patch;
uint8_t volume, expression;
uint8_t panning, vibrato, sustain;
char ____padding[6];
double bend, bendsense;
double vibpos, vibspeed, vibdepth;
int64_t vibdelay;
uint8_t lastlrpn, lastmrpn;
bool nrpn;
uint8_t brightness;
bool is_xg_percussion;
struct NoteInfo
{
// Current pressure
uint8_t vol;
char ____padding[1];
// Tone selected on noteon:
int16_t tone;
char ____padding2[4];
// Patch selected on noteon; index to banks[AdlBank][]
size_t midiins;
// Index to physical adlib data structure, adlins[]
size_t insmeta;
typedef std::map<uint16_t, uint16_t> PhysMap;
typedef uint16_t Phys;
// List of OPN2 channels it is currently occupying.
std::map<uint16_t /*adlchn*/, Phys> phys;
};
typedef std::map<uint8_t, NoteInfo> activenotemap_t;
typedef activenotemap_t::iterator activenoteiterator;
char ____padding2[5];
activenotemap_t activenotes;
void reset()
{
portamento = 0;
bank_lsb = 0;
bank_msb = 0;
patch = 0;
volume = 100;
expression = 127;
panning = 0xC0;
vibrato = 0;
sustain = 0;
bend = 0.0;
bendsense = 2 / 8192.0;
vibpos = 0;
vibspeed = 2 * 3.141592653 * 5.0;
vibdepth = 0.5 / 127;
vibdelay = 0;
lastlrpn = 0;
lastmrpn = 0;
nrpn = false;
brightness = 127;
is_xg_percussion = false;
}
MIDIchannel()
: activenotes()
{
reset();
}
};
// Additional information about OPN channels
struct OpnChannel
{
// For collisions
struct Location
{
uint16_t MidCh;
uint8_t note;
bool operator==(const Location &b) const
{
return MidCh == b.MidCh && note == b.note;
}
bool operator< (const Location &b) const
{
return MidCh < b.MidCh || (MidCh == b.MidCh && note < b.note);
}
char ____padding[1];
};
struct LocationData
{
bool sustained;
char ____padding[1];
MIDIchannel::NoteInfo::Phys ins; // a copy of that in phys[]
char ____padding2[4];
int64_t kon_time_until_neglible;
int64_t vibdelay;
};
typedef std::map<Location, LocationData> users_t;
users_t users;
// If the channel is keyoff'd
int64_t koff_time_until_neglible;
// For channel allocation:
OpnChannel(): users(), koff_time_until_neglible(0) { }
void AddAge(int64_t ms);
};
/**
* @brief MIDI Event utility container
*/
class MidiEvent
{
public:
MidiEvent();
enum Types
{
T_UNKNOWN = 0x00,
T_NOTEOFF = 0x08,//size == 2
T_NOTEON = 0x09,//size == 2
T_NOTETOUCH = 0x0A,//size == 2
T_CTRLCHANGE = 0x0B,//size == 2
T_PATCHCHANGE = 0x0C,//size == 1
T_CHANAFTTOUCH = 0x0D,//size == 1
T_WHEEL = 0x0E,//size == 2
T_SYSEX = 0xF0,//size == len
T_SYSCOMSPOSPTR = 0xF2,//size == 2
T_SYSCOMSNGSEL = 0xF3,//size == 1
T_SYSEX2 = 0xF7,//size == len
T_SPECIAL = 0xFF
};
enum SubTypes
{
ST_SEQNUMBER = 0x00,//size == 2
ST_TEXT = 0x01,//size == len
ST_COPYRIGHT = 0x02,//size == len
ST_SQTRKTITLE = 0x03,//size == len
ST_INSTRTITLE = 0x04,//size == len
ST_LYRICS = 0x05,//size == len
ST_MARKER = 0x06,//size == len
ST_CUEPOINT = 0x07,//size == len
ST_DEVICESWITCH = 0x09,//size == len <CUSTOM>
ST_MIDICHPREFIX = 0x20,//size == 1
ST_ENDTRACK = 0x2F,//size == 0
ST_TEMPOCHANGE = 0x51,//size == 3
ST_SMPTEOFFSET = 0x54,//size == 5
ST_TIMESIGNATURE = 0x55, //size == 4
ST_KEYSIGNATURE = 0x59,//size == 2
ST_SEQUENCERSPEC = 0x7F, //size == len
/* Non-standard, internal ADLMIDI usage only */
ST_LOOPSTART = 0xE1,//size == 0 <CUSTOM>
ST_LOOPEND = 0xE2,//size == 0 <CUSTOM>
ST_RAWOPL = 0xE3//size == 0 <CUSTOM>
};
//! Main type of event
uint8_t type;
//! Sub-type of the event
uint8_t subtype;
//! Targeted MIDI channel
uint8_t channel;
//! Is valid event
uint8_t isValid;
//! Reserved 5 bytes padding
uint8_t __padding[4];
//! Absolute tick position (Used for the tempo calculation only)
uint64_t absPosition;
//! Raw data of this event
std::vector<uint8_t> data;
};
/**
* @brief A track position event contains a chain of MIDI events until next delay value
*
* Created with purpose to sort events by type in the same position
* (for example, to keep controllers always first than note on events or lower than note-off events)
*/
class MidiTrackRow
{
public:
MidiTrackRow();
void reset();
//! Absolute time position in seconds
double time;
//! Delay to next event in ticks
uint64_t delay;
//! Absolute position in ticks
uint64_t absPos;
//! Delay to next event in seconds
double timeDelay;
std::vector<MidiEvent> events;
/**
* @brief Sort events in this position
*/
void sortEvents(bool *noteStates = NULL);
};
/**
* @brief Tempo change point entry. Used in the MIDI data building function only.
*/
struct TempoChangePoint
{
uint64_t absPos;
fraction<uint64_t> tempo;
};
//P.S. I declared it here instead of local in-function because C++99 can't process templates with locally-declared structures
typedef std::list<MidiTrackRow> MidiTrackQueue;
// Information about each track
struct PositionNew
{
bool began;
char padding[7];
double wait;
double absTimePosition;
struct TrackInfo
{
size_t ptr;
uint64_t delay;
int status;
char padding2[4];
MidiTrackQueue::iterator pos;
TrackInfo(): ptr(0), delay(0), status(0) {}
};
std::vector<TrackInfo> track;
PositionNew(): began(false), wait(0.0), absTimePosition(0.0), track()
{}
};
struct Setup
{
unsigned int OpnBank;
unsigned int NumCards;
unsigned int LogarithmicVolumes;
int VolumeModel;
//unsigned int SkipForward;
bool loopingIsEnabled;
int ScaleModulators;
double delay;
double carry;
/* The lag between visual content and audio content equals */
/* the sum of these two buffers. */
double mindelay;
double maxdelay;
/* For internal usage */
ssize_t tick_skip_samples_delay; /* Skip tick processing after samples count. */
/* For internal usage */
unsigned long PCM_RATE;
};
struct MIDI_MarkerEntry
{
std::string label;
double pos_time;
uint64_t pos_ticks;
};
std::vector<MIDIchannel> Ch;
//bool cmf_percussion_mode;
MIDIEventHooks hooks;
private:
std::map<std::string, uint64_t> devices;
std::map<uint64_t /*track*/, uint64_t /*channel begin index*/> current_device;
std::vector<OpnChannel> ch;
std::vector<std::vector<uint8_t> > TrackData;
PositionNew CurrentPositionNew, LoopBeginPositionNew, trackBeginPositionNew;
//! Full song length in seconds
double fullSongTimeLength;
//! Delay after song playd before rejecting the output stream requests
double postSongWaitDelay;
//! Loop start time
double loopStartTime;
//! Loop end time
double loopEndTime;
//! Local error string
std::string errorString;
//! Local error string
std::string errorStringOut;
//! Pre-processed track data storage
std::vector<MidiTrackQueue > trackDataNew;
//! Missing instruments catches
std::set<uint8_t> caugh_missing_instruments;
//! Missing melodic banks catches
std::set<uint16_t> caugh_missing_banks_melodic;
//! Missing percussion banks catches
std::set<uint16_t> caugh_missing_banks_percussion;
/**
* @brief Build MIDI track data from the raw track data storage
* @return true if everything successfully processed, or false on any error
*/
bool buildTrackData();
/**
* @brief Parse one event from raw MIDI track stream
* @param [_inout] ptr pointer to pointer to current position on the raw data track
* @param [_in] end address to end of raw track data, needed to validate position and size
* @param [_inout] status status of the track processing
* @return Parsed MIDI event entry
*/
MidiEvent parseEvent(uint8_t **ptr, uint8_t *end, int &status);
public:
const std::string &getErrorString();
void setErrorString(const std::string &err);
std::string musTitle;
std::string musCopyright;
std::vector<std::string> musTrackTitles;
std::vector<MIDI_MarkerEntry> musMarkers;
fraction<uint64_t> InvDeltaTicks, Tempo;
//! Tempo multiplier
double tempoMultiplier;
bool atEnd,
loopStart,
loopEnd,
invalidLoop; /*Loop points are invalid (loopStart after loopEnd or loopStart and loopEnd are on same place)*/
char ____padding2[2];
OPN2 opn;
int16_t outBuf[1024];
Setup m_setup;
static uint64_t ReadBEint(const void *buffer, size_t nbytes);
static uint64_t ReadLEint(const void *buffer, size_t nbytes);
/**
* @brief Standard MIDI Variable-Length numeric value parser without of validation
* @param [_inout] ptr Pointer to memory block that contains begin of variable-length value
* @return Unsigned integer that conains parsed variable-length value
*/
uint64_t ReadVarLen(uint8_t **ptr);
/**
* @brief Secure Standard MIDI Variable-Length numeric value parser with anti-out-of-range protection
* @param [_inout] ptr Pointer to memory block that contains begin of variable-length value, will be iterated forward
* @param [_in end Pointer to end of memory block where variable-length value is stored (after end of track)
* @param [_out] ok Reference to boolean which takes result of variable-length value parsing
* @return Unsigned integer that conains parsed variable-length value
*/
uint64_t ReadVarLenEx(uint8_t **ptr, uint8_t *end, bool &ok);
bool LoadBank(const std::string &filename);
bool LoadBank(const void *data, size_t size);
bool LoadBank(fileReader &fr);
bool LoadMIDI(const std::string &filename);
bool LoadMIDI(const void *data, size_t size);
bool LoadMIDI(fileReader &fr);
/**
* @brief Periodic tick handler.
* @param s seconds since last call
* @param granularity don't expect intervals smaller than this, in seconds
* @return desired number of seconds until next call
*/
double Tick(double s, double granularity);
/**
* @brief Process extra iterators like vibrato or arpeggio
* @param s seconds since last call
*/
void TickIteratos(double s);
/**
* @brief Change current position to specified time position in seconds
* @param seconds Absolute time position in seconds
*/
void seek(double seconds);
/**
* @brief Gives current time position in seconds
* @return Current time position in seconds
*/
double tell();
/**
* @brief Gives time length of current song in seconds
* @return Time length of current song in seconds
*/
double timeLength();
/**
* @brief Gives loop start time position in seconds
* @return Loop start time position in seconds or -1 if song has no loop points
*/
double getLoopStart();
/**
* @brief Gives loop end time position in seconds
* @return Loop end time position in seconds or -1 if song has no loop points
*/
double getLoopEnd();
/**
* @brief Return to begin of current song
*/
void rewind();
/**
* @brief Set tempo multiplier
* @param tempo Tempo multiplier: 1.0 - original tempo. >1 - faster, <1 - slower
*/
void setTempo(double tempo);
/* RealTime event triggers */
void realTime_ResetState();
bool realTime_NoteOn(uint8_t channel, uint8_t note, uint8_t velocity);
void realTime_NoteOff(uint8_t channel, uint8_t note);
void realTime_NoteAfterTouch(uint8_t channel, uint8_t note, uint8_t atVal);
void realTime_ChannelAfterTouch(uint8_t channel, uint8_t atVal);
void realTime_Controller(uint8_t channel, uint8_t type, uint8_t value);
void realTime_PatchChange(uint8_t channel, uint8_t patch);
void realTime_PitchBend(uint8_t channel, uint16_t pitch);
void realTime_PitchBend(uint8_t channel, uint8_t msb, uint8_t lsb);
void realTime_BankChangeLSB(uint8_t channel, uint8_t lsb);
void realTime_BankChangeMSB(uint8_t channel, uint8_t msb);
void realTime_BankChange(uint8_t channel, uint16_t bank);
void realTime_panic();
private:
enum
{
Upd_Patch = 0x1,
Upd_Pan = 0x2,
Upd_Volume = 0x4,
Upd_Pitch = 0x8,
Upd_All = Upd_Pan + Upd_Volume + Upd_Pitch,
Upd_Off = 0x20
};
void NoteUpdate(uint16_t MidCh,
MIDIchannel::activenoteiterator i,
unsigned props_mask,
int32_t select_adlchn = -1);
bool ProcessEventsNew(bool isSeek = false);
void HandleEvent(size_t tk, const MidiEvent &evt, int &status);
// Determine how good a candidate this adlchannel
// would be for playing a note from this instrument.
long CalculateAdlChannelGoodness(size_t c, uint16_t ins, uint16_t /*MidCh*/) const;
// A new note will be played on this channel using this instrument.
// Kill existing notes on this channel (or don't, if we do arpeggio)
void PrepareAdlChannelForNewNote(size_t c, size_t ins);
void KillOrEvacuate(
size_t from_channel,
OpnChannel::users_t::iterator j,
MIDIchannel::activenoteiterator i);
void Panic();
void KillSustainingNotes(int32_t MidCh = -1, int32_t this_adlchn = -1);
void SetRPN(unsigned MidCh, unsigned value, bool MSB);
//void UpdatePortamento(unsigned MidCh)
void NoteUpdate_All(uint16_t MidCh, unsigned props_mask);
void NoteOff(uint16_t MidCh, uint8_t note);
void UpdateVibrato(double amount);
void UpdateArpeggio(double /*amount*/);
public:
uint64_t ChooseDevice(const std::string &name);
};
extern int opn2RefreshNumCards(OPN2_MIDIPlayer *device);
#endif // ADLMIDI_PRIVATE_HPP

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,60 @@
/*
* XMIDI: Miles XMIDI to MID Library Header
*
* Copyright (C) 2001 Ryan Nunn
* Copyright (C) 2014-2016 Bret Curtis
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public
* License along with this library; if not, write to the
* Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
/* XMIDI Converter */
#ifndef XMIDILIB_H
#define XMIDILIB_H
#include <stdint.h>
#ifdef __DJGPP__
typedef signed char int8_t;
typedef unsigned char uint8_t;
typedef signed short int16_t;
typedef unsigned short uint16_t;
typedef signed long int32_t;
typedef unsigned long uint32_t;
#endif
#ifdef __cplusplus
extern "C"
{
#endif
/* Conversion types for Midi files */
#define XMIDI_CONVERT_NOCONVERSION 0x00
#define XMIDI_CONVERT_MT32_TO_GM 0x01
#define XMIDI_CONVERT_MT32_TO_GS 0x02
#define XMIDI_CONVERT_MT32_TO_GS127 0x03 /* This one is broken, don't use */
#define XMIDI_CONVERT_MT32_TO_GS127DRUM 0x04 /* This one is broken, don't use */
#define XMIDI_CONVERT_GS127_TO_GS 0x05
int OpnMidi_xmi2midi(uint8_t *in, uint32_t insize,
uint8_t **out, uint32_t *outsize,
uint32_t convert_type);
#ifdef __cplusplus
}
#endif
#endif /* XMIDILIB_H */