mirror of
https://github.com/ZDoom/gzdoom.git
synced 2024-12-01 16:32:17 +00:00
Added libOPNMIDI sources
This commit is contained in:
parent
3072c9bf7c
commit
46942cb27f
15 changed files with 8776 additions and 0 deletions
1319
src/sound/opnmidi/Ym2612_ChipEmu.cpp
Normal file
1319
src/sound/opnmidi/Ym2612_ChipEmu.cpp
Normal file
File diff suppressed because it is too large
Load diff
38
src/sound/opnmidi/Ym2612_ChipEmu.h
Normal file
38
src/sound/opnmidi/Ym2612_ChipEmu.h
Normal 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
|
206
src/sound/opnmidi/fraction.hpp
Normal file
206
src/sound/opnmidi/fraction.hpp
Normal 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
|
81
src/sound/opnmidi/opnbank.h
Normal file
81
src/sound/opnmidi/opnbank.h
Normal 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;
|
||||||
|
};
|
776
src/sound/opnmidi/opnmidi.cpp
Normal file
776
src/sound/opnmidi/opnmidi.cpp
Normal 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
266
src/sound/opnmidi/opnmidi.h
Normal 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 */
|
557
src/sound/opnmidi/opnmidi_load.cpp
Normal file
557
src/sound/opnmidi/opnmidi_load.cpp
Normal 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;
|
||||||
|
}
|
2600
src/sound/opnmidi/opnmidi_midiplay.cpp
Normal file
2600
src/sound/opnmidi/opnmidi_midiplay.cpp
Normal file
File diff suppressed because it is too large
Load diff
451
src/sound/opnmidi/opnmidi_mus2mid.c
Normal file
451
src/sound/opnmidi/opnmidi_mus2mid.c
Normal 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);
|
||||||
|
}
|
||||||
|
|
49
src/sound/opnmidi/opnmidi_mus2mid.h
Normal file
49
src/sound/opnmidi/opnmidi_mus2mid.h
Normal 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 */
|
318
src/sound/opnmidi/opnmidi_opn2.cpp
Normal file
318
src/sound/opnmidi/opnmidi_opn2.cpp
Normal 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();
|
||||||
|
}
|
55
src/sound/opnmidi/opnmidi_private.cpp
Normal file
55
src/sound/opnmidi/opnmidi_private.cpp
Normal 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;
|
||||||
|
}
|
894
src/sound/opnmidi/opnmidi_private.hpp
Normal file
894
src/sound/opnmidi/opnmidi_private.hpp
Normal 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
|
1106
src/sound/opnmidi/opnmidi_xmi2mid.c
Normal file
1106
src/sound/opnmidi/opnmidi_xmi2mid.c
Normal file
File diff suppressed because it is too large
Load diff
60
src/sound/opnmidi/opnmidi_xmi2mid.h
Normal file
60
src/sound/opnmidi/opnmidi_xmi2mid.h
Normal 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 */
|
Loading…
Reference in a new issue