Merge remote-tracking branch 'origin/master' into asmjit

This commit is contained in:
Magnus Norddahl 2018-10-07 06:32:13 +02:00
commit c5a5265e40
126 changed files with 16485 additions and 12909 deletions

BIN
fm_banks/GENMIDI.GS.wopl Normal file

Binary file not shown.

Binary file not shown.

View file

@ -633,12 +633,9 @@ if( NOT SEND_ANON_STATS )
add_definitions( -DNO_SEND_STATS )
endif()
# OPLMIDI needs for USE_LEGACY_EMULATOR macro to be correctly built
add_definitions(-DOPNMIDI_USE_LEGACY_EMULATOR)
# Disable ADLMIDI's and OPNMIDI's MIDI Sequencer, MUS and XMI converters
add_definitions(-DADLMIDI_DISABLE_MUS_SUPPORT -DADLMIDI_DISABLE_XMI_SUPPORT -DADLMIDI_DISABLE_MIDI_SEQUENCER)
add_definitions(-DOPNMIDI_DISABLE_MUS_SUPPORT -DOPNMIDI_DISABLE_XMI_SUPPORT -DOPNMIDI_DISABLE_MIDI_SEQUENCER)
# Disable ADLMIDI's and OPNMIDI's MIDI Sequencer
add_definitions(-DADLMIDI_DISABLE_MIDI_SEQUENCER)
add_definitions(-DOPNMIDI_DISABLE_MIDI_SEQUENCER)
# Disable OPNMIDI's experimental yet emulator (using of it has some issues and missing notes in playback)
add_definitions(-DOPNMIDI_DISABLE_GX_EMULATOR)
@ -876,6 +873,7 @@ set( FASTMATH_SOURCES
sound/opnmidi/opnmidi_midiplay.cpp
sound/opnmidi/opnmidi_opn2.cpp
sound/opnmidi/opnmidi_private.cpp
sound/opnmidi/wopn/wopn_file.c
)
@ -1359,7 +1357,12 @@ endif()
add_custom_command(TARGET zdoom POST_BUILD
COMMAND ${CMAKE_COMMAND} -E copy_if_different
${CMAKE_SOURCE_DIR}/soundfont/gzdoom.sf2 $<TARGET_FILE_DIR:zdoom>/soundfonts/gzdoom.sf2)
${CMAKE_SOURCE_DIR}/soundfont/gzdoom.sf2 $<TARGET_FILE_DIR:zdoom>/soundfonts/gzdoom.sf2
COMMAND ${CMAKE_COMMAND} -E copy_if_different
${CMAKE_SOURCE_DIR}/fm_banks/GENMIDI.GS.wopl $<TARGET_FILE_DIR:zdoom>/fm_banks/GENMIDI.GS.wopl
COMMAND ${CMAKE_COMMAND} -E copy_if_different
${CMAKE_SOURCE_DIR}/fm_banks/gs-by-papiezak-and-sneakernets.wopn $<TARGET_FILE_DIR:zdoom>/fm_banks/gs-by-papiezak-and-sneakernets.wopn
)
if( CMAKE_COMPILER_IS_GNUCXX )
# GCC misoptimizes this file

View file

@ -45,6 +45,7 @@
#include "dobject.h"
#include "vm.h"
#include "i_time.h"
#include "menu/menu.h"
const char *KeyNames[NUM_KEYS] =
{
@ -267,6 +268,14 @@ DEFINE_ACTION_FUNCTION(FKeyBindings, SetBind)
PARAM_SELF_STRUCT_PROLOGUE(FKeyBindings);
PARAM_INT(k);
PARAM_STRING(cmd);
// Only menus are allowed to change bindings.
if (DMenu::InMenu == 0)
{
I_FatalError("Attempt to change key bindings outside of menu code to '%s'", cmd.GetChars());
}
self->SetBind(k, cmd);
return 0;
}
@ -506,6 +515,13 @@ DEFINE_ACTION_FUNCTION(FKeyBindings, UnbindACommand)
{
PARAM_SELF_STRUCT_PROLOGUE(FKeyBindings);
PARAM_STRING(cmd);
// Only menus are allowed to change bindings.
if (DMenu::InMenu == 0)
{
I_FatalError("Attempt to unbind key bindings for '%s' outside of menu code", cmd.GetChars());
}
self->UnbindACommand(cmd);
return 0;
}

View file

@ -220,7 +220,14 @@ DEFINE_ACTION_FUNCTION(_CVar, SetInt)
{
// Only menus are allowed to change CVARs.
PARAM_SELF_STRUCT_PROLOGUE(FBaseCVar);
if (!(self->GetFlags() & CVAR_MOD) && CurrentMenu == nullptr) return 0;
if (!(self->GetFlags() & CVAR_MOD))
{
// Only menus are allowed to change non-mod CVARs.
if (DMenu::InMenu == 0)
{
I_FatalError("Attempt to change CVAR '%s' outside of menu code", self->GetName());
}
}
PARAM_INT(val);
UCVarValue v;
v.Int = val;
@ -230,9 +237,15 @@ DEFINE_ACTION_FUNCTION(_CVar, SetInt)
DEFINE_ACTION_FUNCTION(_CVar, SetFloat)
{
// Only menus are allowed to change CVARs.
PARAM_SELF_STRUCT_PROLOGUE(FBaseCVar);
if (!(self->GetFlags() & CVAR_MOD) && CurrentMenu == nullptr) return 0;
if (!(self->GetFlags() & CVAR_MOD))
{
// Only menus are allowed to change non-mod CVARs.
if (DMenu::InMenu == 0)
{
I_FatalError("Attempt to change CVAR '%s' outside of menu code", self->GetName());
}
}
PARAM_FLOAT(val);
UCVarValue v;
v.Float = (float)val;
@ -244,7 +257,14 @@ DEFINE_ACTION_FUNCTION(_CVar, SetString)
{
// Only menus are allowed to change CVARs.
PARAM_SELF_STRUCT_PROLOGUE(FBaseCVar);
if (!(self->GetFlags() & CVAR_MOD) && CurrentMenu == nullptr) return 0;
if (!(self->GetFlags() & CVAR_MOD))
{
// Only menus are allowed to change non-mod CVARs.
if (DMenu::InMenu == 0)
{
I_FatalError("Attempt to change CVAR '%s' outside of menu code", self->GetName());
}
}
PARAM_STRING(val);
UCVarValue v;
v.String = val.GetChars();

View file

@ -515,6 +515,14 @@ bool E_CheckReplacement( PClassActor *replacee, PClassActor **replacement )
return final;
}
void E_NewGame()
{
for (DStaticEventHandler* handler = E_FirstEventHandler; handler; handler = handler->next)
{
handler->NewGame();
}
}
// normal event loopers (non-special, argument-less)
DEFINE_EVENT_LOOPER(RenderFrame)
DEFINE_EVENT_LOOPER(WorldLightning)
@ -668,6 +676,8 @@ DEFINE_EMPTY_HANDLER(DStaticEventHandler, NetworkProcess);
DEFINE_EMPTY_HANDLER(DStaticEventHandler, CheckReplacement);
DEFINE_EMPTY_HANDLER(DStaticEventHandler, NewGame)
// ===========================================
//
// Event handlers
@ -1154,6 +1164,18 @@ void DStaticEventHandler::CheckReplacement( PClassActor *replacee, PClassActor *
}
}
void DStaticEventHandler::NewGame()
{
IFVIRTUAL(DStaticEventHandler, NewGame)
{
// don't create excessive DObjects if not going to be processed anyway
if (func == DStaticEventHandler_NewGame_VMPtr)
return;
VMValue params[1] = { (DStaticEventHandler*)this };
VMCall(func, params, 1, nullptr, 0);
}
}
//
void DStaticEventHandler::OnDestroy()
{

View file

@ -72,6 +72,9 @@ void E_Console(int player, FString name, int arg1, int arg2, int arg3, bool manu
// called when looking up the replacement for an actor class
bool E_CheckReplacement(PClassActor* replacee, PClassActor** replacement);
// called on new game
void E_NewGame();
// send networked event. unified function.
bool E_SendNetworkEvent(FString name, int arg1, int arg2, int arg3, bool manual);
@ -172,6 +175,9 @@ public:
//
void CheckReplacement(PClassActor* replacee, PClassActor** replacement, bool* final);
//
void NewGame();
};
class DEventHandler : public DStaticEventHandler
{

View file

@ -527,6 +527,10 @@ void G_InitNew (const char *mapname, bool bTitleLevel)
gamestate = GS_LEVEL;
}
G_DoLoadLevel (0, false);
if(!savegamerestore)
{
E_NewGame();
}
}
//

View file

@ -136,17 +136,27 @@ FGameConfigFile::FGameConfigFile ()
SetSection("SoundfontSearch.Directories", true);
#ifdef __APPLE__
SetValueForKey("Path", user_docs + "/soundfonts", true);
SetValueForKey("Path", user_docs + "/fm_banks", true);
SetValueForKey("Path", user_app_support + "/soundfonts", true);
SetValueForKey("Path", user_app_support + "/fm_banks", true);
SetValueForKey("Path", "$PROGDIR/soundfonts", true);
SetValueForKey("Path", "$PROGDIR/fm_banks", true);
SetValueForKey("Path", local_app_support + "/soundfonts", true);
SetValueForKey("Path", local_app_support + "/fm_banks", true);
#elif !defined(__unix__)
SetValueForKey("Path", "$PROGDIR/soundfonts", true);
SetValueForKey("Path", "$PROGDIR/fm_banks", true);
#else
SetValueForKey("Path", "$HOME/" GAME_DIR "/soundfonts", true);
SetValueForKey("Path", "$HOME/" GAME_DIR "/fm_banks", true);
SetValueForKey("Path", "/usr/local/share/doom/soundfonts", true);
SetValueForKey("Path", "/usr/local/share/doom/fm_banks", true);
SetValueForKey("Path", "/usr/local/share/games/doom/soundfonts", true);
SetValueForKey("Path", "/usr/local/share/games/doom/fm_banks", true);
SetValueForKey("Path", "/usr/share/doom/soundfonts", true);
SetValueForKey("Path", "/usr/share/doom/fm_banks", true);
SetValueForKey("Path", "/usr/share/games/doom/soundfonts", true);
SetValueForKey("Path", "/usr/share/games/doom/fm_banks", true);
#endif
}

View file

@ -144,7 +144,6 @@ void GLViewpointBuffer::Set2D(int width, int height)
HWViewpointUniforms matrices;
matrices.SetDefaults();
matrices.mProjectionMatrix.ortho(0, width, height, 0, -1.0f, 1.0f);
matrices.mFogEnabled = 3;
matrices.CalcDependencies();
Map();
memcpy(mBufferPointer, &matrices, sizeof(matrices));

View file

@ -119,7 +119,7 @@ bool FRenderState::ApplyShader()
static uint64_t firstFrame = 0;
// if firstFrame is not yet initialized, initialize it to current time
// if we're going to overflow a float (after ~4.6 hours, or 24 bits), re-init to regain precision
if ((firstFrame == 0) || (screen->FrameTime - firstFrame >= 1 << 24) || level.ShaderStartTime >= firstFrame)
if ((firstFrame == 0) || (screen->FrameTime - firstFrame >= 1<<24) || level.ShaderStartTime >= firstFrame)
firstFrame = screen->FrameTime;
static const float nulvec[] = { 0.f, 0.f, 0.f, 0.f };
@ -133,15 +133,31 @@ bool FRenderState::ApplyShader()
activeShader->Bind();
}
int fogset = 0;
if (mFogEnabled)
{
if (mFogEnabled == 2)
{
fogset = -3; // 2D rendering with 'foggy' overlay.
}
else if ((mFogColor & 0xffffff) == 0)
{
fogset = gl_fogmode;
}
else
{
fogset = -gl_fogmode;
}
}
glVertexAttrib4fv(VATTR_COLOR, mColor.vec);
glVertexAttrib4fv(VATTR_NORMAL, mNormal.vec);
activeShader->muDesaturation.Set(mDesaturation / 255.f);
activeShader->muFogEnabled.Set(fogset);
activeShader->muTextureMode.Set(mTextureMode == TM_MODULATE && mTempTM == TM_OPAQUE ? TM_OPAQUE : mTextureMode);
float fds = mLightParms[2];
if (!mFogEnabled) mLightParms[2] = 0;
activeShader->muLightParms.Set(mLightParms);
mLightParms[2] = fds;
activeShader->muFogColor.Set(mFogColor);
activeShader->muObjectColor.Set(mObjectColor);
activeShader->muObjectColor2.Set(mObjectColor2);

View file

@ -57,6 +57,7 @@ void FDrawInfo::SetupSubsectorLights(GLFlat *flat, int pass, subsector_t * sub,
{
if (dli != NULL && *dli != -1)
{
if (flat->renderstyle == STYLE_Add && !level.lightadditivesurfaces) return; // no lights on additively blended surfaces.
gl_RenderState.ApplyLightIndex(GLRenderer->mLights->GetIndex(*dli));
(*dli)++;
return;
@ -85,6 +86,7 @@ void FDrawInfo::SetupSectorLights(GLFlat *flat, int pass, int *dli)
{
if (dli != NULL && *dli != -1)
{
if (flat->renderstyle == STYLE_Add && !level.lightadditivesurfaces) return; // no lights on additively blended surfaces.
gl_RenderState.ApplyLightIndex(GLRenderer->mLights->GetIndex(*dli));
(*dli)++;
return;

View file

@ -375,7 +375,6 @@ void FDrawInfo::DrawEndScene2D(sector_t * viewsector)
HWViewpointUniforms vp = VPUniforms;
vp.mViewMatrix.loadIdentity();
vp.mProjectionMatrix = vrmode->GetHUDSpriteProjection();
vp.mFogEnabled = 0;
GLRenderer->mViewpoints->SetViewpoint(&vp);
glDisable(GL_DEPTH_TEST);
glDisable(GL_MULTISAMPLE);

View file

@ -68,8 +68,7 @@ bool FShader::Load(const char * name, const char * vert_prog_lump, const char *
int uViewHeight; // Software fuzz scaling
float uClipHeight;
float uClipHeightDirection;
int uFogEnabled;
int uShadowmapFilter;
};
)";
@ -100,6 +99,7 @@ bool FShader::Load(const char * name, const char * vert_prog_lump, const char *
i_data += "#define uFogDensity uLightAttr.b\n";
i_data += "#define uLightFactor uLightAttr.g\n";
i_data += "#define uLightDist uLightAttr.r\n";
i_data += "uniform int uFogEnabled;\n";
// dynamic lights
i_data += "uniform int uLightIndex;\n";
@ -331,6 +331,7 @@ bool FShader::Load(const char * name, const char * vert_prog_lump, const char *
muDesaturation.Init(hShader, "uDesaturationFactor");
muFogEnabled.Init(hShader, "uFogEnabled");
muTextureMode.Init(hShader, "uTextureMode");
muLightParms.Init(hShader, "uLightAttr");
muClipSplit.Init(hShader, "uClipSplit");

View file

@ -242,6 +242,7 @@ class FShader
FName mName;
FBufferedUniform1f muDesaturation;
FBufferedUniform1i muFogEnabled;
FBufferedUniform1i muTextureMode;
FBufferedUniform4f muLightParms;
FBufferedUniform2f muClipSplit;

View file

@ -280,6 +280,6 @@ void HWViewpointUniforms::SetDefaults()
mGlobVis = (float)R_GetGlobVis(r_viewwindow, r_visibility) / 32.f;
mPalLightLevels = static_cast<int>(gl_bandedswlight) | (static_cast<int>(gl_fogmode) << 8);
mClipLine.X = -10000000.0f;
mFogEnabled = gl_fogmode;
mShadowmapFilter = gl_shadowmap_filter;
}

View file

@ -139,9 +139,9 @@ bool GLFlat::SetupLights(int pass, FLightNode * node, FDynLightData &lightdata,
{
Plane p;
lightdata.Clear();
if (renderstyle == STYLE_Add && !level.lightadditivesurfaces) return false; // no lights on additively blended surfaces.
lightdata.Clear();
while (node)
{
ADynamicLight * light = node->lightsource;

View file

@ -16,7 +16,7 @@ struct HWViewpointUniforms
int mViewHeight = 0;
float mClipHeight = 0.f;
float mClipHeightDirection = 0.f;
int mFogEnabled = 0;
int mShadowmapFilter = 1;
void CalcDependencies()
{

View file

@ -169,7 +169,7 @@ void CheckBench()
AppendRenderTimes(compose);
AppendLightStats(compose);
//AppendMissingTextureStats(compose);
compose.AppendFormat("%llu fps\n\n", screen->GetLastFPS());
compose.AppendFormat("%llu fps\n\n", (unsigned long long)screen->GetLastFPS());
FILE *f = fopen("benchmarks.txt", "at");
if (f != NULL)

View file

@ -132,3 +132,8 @@ CUSTOM_CVAR(Int, gl_fuzztype, 0, CVAR_ARCHIVE)
{
if (self < 0 || self > 8) self = 0;
}
CUSTOM_CVAR(Int, gl_shadowmap_filter, 1, CVAR_ARCHIVE | CVAR_GLOBALCONFIG)
{
if (self < 0 || self > 8) self = 1;
}

View file

@ -69,3 +69,5 @@ EXTERN_CVAR(Bool, gl_billboard_faces_camera)
EXTERN_CVAR(Bool, gl_billboard_particles)
EXTERN_CVAR(Int, gl_enhanced_nv_stealth)
EXTERN_CVAR(Int, gl_fuzztype)
EXTERN_CVAR(Int, gl_shadowmap_filter)

View file

@ -438,6 +438,7 @@ PClassActor *PClassActor::GetReplacement(bool lookskill)
}
// The Replacement field is temporarily NULLed to prevent
// potential infinite recursion.
PClassActor *oldrep = ActorInfo()->Replacement;
ActorInfo()->Replacement = nullptr;
PClassActor *rep = Replacement;
// Handle skill-based replacement here. It has precedence on DECORATE replacement
@ -451,7 +452,7 @@ PClassActor *PClassActor::GetReplacement(bool lookskill)
// Skill replacements are not recursive, contrarily to DECORATE replacements
rep = rep->GetReplacement(false);
// Reset the temporarily NULLed field
ActorInfo()->Replacement = Replacement;
ActorInfo()->Replacement = oldrep;
return rep;
}

View file

@ -53,6 +53,7 @@
#include "events.h"
#include "scripting/types.h"
int DMenu::InMenu;
//
// Todo: Move these elsewhere
//
@ -190,7 +191,9 @@ bool DMenu::CallResponder(event_t *ev)
VMValue params[] = { (DObject*)this, &e };
int retval;
VMReturn ret(&retval);
InMenu++;
VMCall(func, params, 2, &ret, 1);
InMenu--;
return !!retval;
}
}
@ -202,7 +205,9 @@ bool DMenu::CallResponder(event_t *ev)
VMValue params[] = { (DObject*)this, &e };
int retval;
VMReturn ret(&retval);
InMenu++;
VMCall(func, params, 2, &ret, 1);
InMenu--;
return !!retval;
}
}
@ -222,7 +227,9 @@ bool DMenu::CallMenuEvent(int mkey, bool fromcontroller)
VMValue params[] = { (DObject*)this, mkey, fromcontroller };
int retval;
VMReturn ret(&retval);
InMenu++;
VMCall(func, params, 3, &ret, 1);
InMenu--;
return !!retval;
}
else return false;

View file

@ -265,6 +265,7 @@ public:
bool mMouseCapture;
bool mBackbuttonSelected;
bool DontDim;
static int InMenu;
DMenu(DMenu *parent = NULL);
bool TranslateKeyboardEvents();

View file

@ -1385,7 +1385,9 @@ static void InitMusicMenus()
std::tuple<const char *, int, const char *> sfmenus[] = { std::make_tuple("GusConfigMenu", SF_SF2 | SF_GUS, "midi_config"),
std::make_tuple("WildMidiConfigMenu", SF_GUS, "wildmidi_config"),
std::make_tuple("TimidityConfigMenu", SF_SF2 | SF_GUS, "timidity_config"),
std::make_tuple("FluidPatchsetMenu", SF_SF2, "fluid_patchset") };
std::make_tuple("FluidPatchsetMenu", SF_SF2, "fluid_patchset"),
std::make_tuple("ADLMIDICustomBanksMenu", SF_WOPL, "adl_custom_bank"),
std::make_tuple("OPNMIDICustomBanksMenu", SF_WOPN, "opn_custom_bank")};
for (auto &p : sfmenus)
{

View file

@ -4552,6 +4552,29 @@ DEFINE_ACTION_FUNCTION(AActor, A_RaiseSiblings)
return 0;
}
//===========================================================================
//
// A_RaiseSelf
//
//===========================================================================
DEFINE_ACTION_FUNCTION(AActor, A_RaiseSelf)
{
PARAM_SELF_PROLOGUE(AActor);
PARAM_INT_DEF(flags);
ACTION_RETURN_BOOL(P_Thing_Raise(self, NULL, (flags & RF_NOCHECKPOSITION)));
}
//===========================================================================
//
// CanRaise
//
//===========================================================================
DEFINE_ACTION_FUNCTION(AActor, CanRaise)
{
PARAM_SELF_PROLOGUE(AActor);
ACTION_RETURN_BOOL(P_Thing_CanRaise(self));
}
//===========================================================================
//
// A_MonsterRefire

View file

@ -2971,6 +2971,12 @@ DEFINE_ACTION_FUNCTION(AActor, A_ExtChase)
return 0;
}
DEFINE_ACTION_FUNCTION(AActor, A_CheckForResurrection)
{
PARAM_SELF_PROLOGUE(AActor);
ACTION_RETURN_BOOL(P_CheckForResurrection(self, false));
}
// for internal use
void A_Chase(AActor *self)
{

View file

@ -453,6 +453,7 @@ enum EDmgFlags
DMG_NO_PROTECT = 256,
DMG_USEANGLE = 512,
DMG_NO_PAIN = 1024,
DMG_EXPLOSION = 2048,
};

View file

@ -4754,12 +4754,19 @@ AActor *P_LineAttack(AActor *t1, DAngle angle, double distance,
// We must pass the unreplaced puff type here
puff = P_SpawnPuff(t1, pufftype, bleedpos, trace.SrcAngleFromTarget, trace.SrcAngleFromTarget - 90, 2, puffFlags | PF_HITTHING, trace.Actor);
}
if (victim != NULL)
{
victim->linetarget = trace.Actor;
victim->attackAngleFromSource = trace.SrcAngleFromTarget;
// With arbitrary portals this cannot be calculated so using the actual attack angle is the only option.
victim->angleFromSource = trace.unlinked ? victim->attackAngleFromSource : t1->AngleTo(trace.Actor);
victim->unlinked = trace.unlinked;
}
if (nointeract)
{
return puff;
}
}
// Allow puffs to inflict poison damage, so that hitscans can poison, too.
if (puffDefaults != NULL && puffDefaults->PoisonDamage > 0 && puffDefaults->PoisonDuration != INT_MIN)
@ -4833,14 +4840,7 @@ AActor *P_LineAttack(AActor *t1, DAngle angle, double distance,
P_TraceBleed(newdam > 0 ? newdam : damage, trace.HitPos, trace.Actor, trace.SrcAngleFromTarget, pitch);
}
}
if (victim != NULL)
{
victim->linetarget = trace.Actor;
victim->attackAngleFromSource = trace.SrcAngleFromTarget;
// With arbitrary portals this cannot be calculated so using the actual attack angle is the only option.
victim->angleFromSource = trace.unlinked? victim->attackAngleFromSource : t1->AngleTo(trace.Actor);
victim->unlinked = trace.unlinked;
}
}
if (trace.Crossed3DWater || trace.CrossedWater)
{
@ -6177,7 +6177,7 @@ int P_RadiusAttack(AActor *bombspot, AActor *bombsource, int bombdamage, int bom
{
//[MC] Don't count actors saved by buddha if already at 1 health.
int prehealth = thing->health;
newdam = P_DamageMobj(thing, bombspot, bombsource, damage, bombmod);
newdam = P_DamageMobj(thing, bombspot, bombsource, damage, bombmod, DMG_EXPLOSION);
if (thing->health < prehealth) count++;
}
else if (thing->player == NULL && (!(flags & RADF_NOIMPACTDAMAGE) && !(thing->flags7 & MF7_DONTTHRUST)))
@ -6229,7 +6229,7 @@ int P_RadiusAttack(AActor *bombspot, AActor *bombsource, int bombdamage, int bom
{ // OK to damage; target is in direct path
//[MC] Don't count actors saved by buddha if already at 1 health.
int prehealth = thing->health;
int newdam = P_DamageMobj(thing, bombspot, bombsource, damage, bombmod);
int newdam = P_DamageMobj(thing, bombspot, bombsource, damage, bombmod, DMG_EXPLOSION);
P_TraceBleed(newdam > 0 ? newdam : damage, thing, bombspot);
if (thing->health < prehealth) count++;
}

View file

@ -1158,6 +1158,14 @@ AInventory *AActor::DropInventory (AInventory *item, int amt)
drop->Vel += Vel;
drop->flags &= ~MF_NOGRAVITY; // Don't float
drop->ClearCounters(); // do not count for statistics again
{
// [MK] call OnDrop so item can change its drop behaviour
IFVIRTUALPTR(drop, AInventory, OnDrop)
{
VMValue params[] = { drop, this };
VMCall(func, params, 2, nullptr, 0);
}
}
return drop;
}
@ -7777,6 +7785,13 @@ void AActor::Revive()
E_WorldThingRevived(this);
}
DEFINE_ACTION_FUNCTION(AActor, Revive)
{
PARAM_SELF_PROLOGUE(AActor);
self->Revive();
return 0;
}
int AActor::GetGibHealth() const
{
IFVIRTUAL(AActor, GetGibHealth)

View file

@ -942,6 +942,8 @@ void G_SerializeLevel(FSerializer &arc, bool hubload)
arc.ReadObjects(hubload);
}
arc("multiplayer", multiplayer);
arc("level.flags", level.flags)
("level.flags2", level.flags2)
("level.fadeto", level.fadeto)

View file

@ -54,7 +54,7 @@ bool FOBJModel::Load(const char* fn, int lumpnum, const char* buffer, int length
}
if (nlpos == -1)
{
nlpos = objBuf.Len();
nlpos = (long)objBuf.Len();
}
FString lineStr(objBuf.GetChars() + bpos, nlpos - bpos);
mtlUsages.Push(lineStr);
@ -72,7 +72,7 @@ bool FOBJModel::Load(const char* fn, int lumpnum, const char* buffer, int length
nlpos = objBuf.IndexOf('\n', bpos);
if (nlpos == -1)
{
nlpos = objBuf.Len();
nlpos = (long)objBuf.Len();
}
memcpy(wObjBuf + bpos, mtlUsages[i].GetChars(), nlpos - bpos);
}
@ -101,8 +101,9 @@ bool FOBJModel::Load(const char* fn, int lumpnum, const char* buffer, int length
FTextureID curMtl = FNullTextureID();
OBJSurface *curSurface = nullptr;
int aggSurfFaceCount = 0;
int curSurfFaceCount = 0;
unsigned int aggSurfFaceCount = 0;
unsigned int curSurfFaceCount = 0;
unsigned int curSmoothGroup = 0;
while(sc.GetString())
{
@ -186,9 +187,25 @@ bool FOBJModel::Load(const char* fn, int lumpnum, const char* buffer, int length
sc.UnGet(); // No 4th side, move back
}
}
face.smoothGroup = curSmoothGroup;
faces.Push(face);
curSurfFaceCount += 1;
}
else if (sc.Compare("s"))
{
sc.MustGetString();
if (sc.Compare("off"))
{
curSmoothGroup = 0;
}
else
{
sc.UnGet();
sc.MustGetNumber();
curSmoothGroup = sc.Number;
hasSmoothGroups = hasSmoothGroups || curSmoothGroup > 0;
}
}
}
sc.Close();
@ -277,11 +294,13 @@ bool FOBJModel::ParseFaceSide(const FString &sideStr, OBJFace &face, int sidx)
else
{
side.normref = -1;
hasMissingNormals = true;
}
}
else
{
side.normref = -1;
hasMissingNormals = true;
}
}
else
@ -289,6 +308,7 @@ bool FOBJModel::ParseFaceSide(const FString &sideStr, OBJFace &face, int sidx)
origIdx = atoi(sideStr.GetChars());
side.vertref = ResolveIndex(origIdx, FaceElement::VertexIndex);
side.normref = -1;
hasMissingNormals = true;
side.uvref = -1;
}
face.sides[sidx] = side;
@ -348,15 +368,20 @@ void FOBJModel::BuildVertexBuffer(FModelRenderer *renderer)
surfaces[i].vbStart = vbufsize;
vbufsize += surfaces[i].numTris * 3;
}
// Initialize/populate vertFaces
if (hasMissingNormals && hasSmoothGroups)
{
AddVertFaces();
}
auto vbuf = renderer->CreateVertexBuffer(false,true);
SetVertexBuffer(renderer, vbuf);
FModelVertex *vertptr = vbuf->LockVertexBuffer(vbufsize);
for (size_t i = 0; i < surfaces.Size(); i++)
for (unsigned int i = 0; i < surfaces.Size(); i++)
{
for (size_t j = 0; j < surfaces[i].numTris; j++)
for (unsigned int j = 0; j < surfaces[i].numTris; j++)
{
for (size_t side = 0; side < 3; side++)
{
@ -372,39 +397,40 @@ void FOBJModel::BuildVertexBuffer(FModelRenderer *renderer)
FVector3 curVvec = RealignVector(verts[vidx]);
FVector2 curUvec = FixUV(uvs[uvidx]);
FVector3 *nvec = nullptr;
FVector3 nvec;
mdv->Set(curVvec.X, curVvec.Y, curVvec.Z, curUvec.X, curUvec.Y);
if (nidx >= 0 && (unsigned int)nidx < norms.Size())
{
nvec = new FVector3(RealignVector(norms[nidx]));
nvec = RealignVector(norms[nidx]);
}
else
{
// https://www.khronos.org/opengl/wiki/Calculating_a_Surface_Normal
// Find other sides of triangle
int nextSidx = side + 2;
if (nextSidx >= 3) nextSidx -= 3;
int lastSidx = side + 1;
if (lastSidx >= 3) lastSidx -= 3;
OBJFaceSide &nextSide = surfaces[i].tris[j].sides[nextSidx];
OBJFaceSide &lastSide = surfaces[i].tris[j].sides[lastSidx];
// Cross-multiply the U-vector and V-vector
FVector3 uvec = RealignVector(verts[nextSide.vertref]) - curVvec;
FVector3 vvec = RealignVector(verts[lastSide.vertref]) - curVvec;
nvec = new FVector3(uvec ^ vvec);
if (surfaces[i].tris[j].smoothGroup == 0)
{
nvec = CalculateNormalFlat(i, j);
}
mdv->SetNormal(nvec->X, nvec->Y, nvec->Z);
delete nvec;
else
{
nvec = CalculateNormalSmooth(vidx, surfaces[i].tris[j].smoothGroup);
}
}
mdv->SetNormal(nvec.X, nvec.Y, nvec.Z);
}
}
delete[] surfaces[i].tris;
}
// Destroy vertFaces
if (hasMissingNormals && hasSmoothGroups)
{
for (size_t i = 0; i < verts.Size(); i++)
{
vertFaces[i].Clear();
}
delete[] vertFaces;
}
vbuf->UnlockVertexBuffer();
}
@ -432,6 +458,7 @@ void FOBJModel::ConstructSurfaceTris(OBJSurface &surf)
surf.tris[triIdx].sideCount = 3;
if (faces[i].sideCount == 3)
{
surf.tris[triIdx].smoothGroup = faces[i].smoothGroup;
memcpy(surf.tris[triIdx].sides, faces[i].sides, sizeof(OBJFaceSide) * 3);
}
else if (faces[i].sideCount == 4) // Triangulate face
@ -443,6 +470,7 @@ void FOBJModel::ConstructSurfaceTris(OBJSurface &surf)
delete[] triangulated;
triIdx += 1; // Filling out two faces
}
DPrintf(DMSG_SPAMMY, "Smooth group: %d\n", surf.tris[triIdx].smoothGroup);
}
}
@ -455,7 +483,9 @@ void FOBJModel::ConstructSurfaceTris(OBJSurface &surf)
void FOBJModel::TriangulateQuad(const OBJFace &quad, OBJFace *tris)
{
tris[0].sideCount = 3;
tris[0].smoothGroup = quad.smoothGroup;
tris[1].sideCount = 3;
tris[1].smoothGroup = quad.smoothGroup;
int tsidx[2][3] = {{0, 1, 3}, {1, 2, 3}};
@ -470,6 +500,26 @@ void FOBJModel::TriangulateQuad(const OBJFace &quad, OBJFace *tris)
}
}
/**
* Add the vertices of all surfaces' triangles to the array of vertex->triangle references
*/
void FOBJModel::AddVertFaces() {
// Initialize and populate vertFaces - this array stores references to triangles per vertex
vertFaces = new TArray<OBJTriRef>[verts.Size()];
for (unsigned int i = 0; i < surfaces.Size(); i++)
{
for (unsigned int j = 0; j < surfaces[i].numTris; j++)
{
OBJTriRef otr = OBJTriRef(i, j);
for (size_t k = 0; k < surfaces[i].tris[j].sideCount; k++)
{
int vidx = surfaces[i].tris[j].sides[k].vertref;
vertFaces[vidx].Push(otr);
}
}
}
}
/**
* Re-align a vector to match MD3 alignment
*
@ -494,6 +544,65 @@ inline FVector2 FOBJModel::FixUV(FVector2 vecToRealign)
return vecToRealign;
}
/**
* Calculate the surface normal for a triangle
*
* @param surfIdx The surface index
* @param triIdx The triangle Index
* @return The surface normal vector
*/
FVector3 FOBJModel::CalculateNormalFlat(unsigned int surfIdx, unsigned int triIdx)
{
// https://www.khronos.org/opengl/wiki/Calculating_a_Surface_Normal
int curVert = surfaces[surfIdx].tris[triIdx].sides[0].vertref;
int nextVert = surfaces[surfIdx].tris[triIdx].sides[2].vertref;
int lastVert = surfaces[surfIdx].tris[triIdx].sides[1].vertref;
// Cross-multiply the U-vector and V-vector
FVector3 curVvec = RealignVector(verts[curVert]);
FVector3 uvec = RealignVector(verts[nextVert]) - curVvec;
FVector3 vvec = RealignVector(verts[lastVert]) - curVvec;
return uvec ^ vvec;
}
/**
* Calculate the surface normal for a triangle
*
* @param otr A reference to the surface, and a triangle within that surface, as an OBJTriRef
* @return The surface normal vector
*/
FVector3 FOBJModel::CalculateNormalFlat(OBJTriRef otr)
{
return CalculateNormalFlat(otr.surf, otr.tri);
}
/**
* Calculate the normal of a vertex in a specific smooth group
*
* @param vidx The index of the vertex in the array of vertices
* @param smoothGroup The smooth group number
*/
FVector3 FOBJModel::CalculateNormalSmooth(unsigned int vidx, unsigned int smoothGroup)
{
unsigned int connectedFaces = 0;
TArray<OBJTriRef>& vTris = vertFaces[vidx];
FVector3 vNormal(0,0,0);
for (size_t face = 0; face < vTris.Size(); face++)
{
OBJFace& tri = surfaces[vTris[face].surf].tris[vTris[face].tri];
if (tri.smoothGroup == smoothGroup)
{
FVector3 fNormal = CalculateNormalFlat(vTris[face]);
connectedFaces += 1;
vNormal += fNormal;
}
}
vNormal /= (float)connectedFaces;
return vNormal;
}
/**
* Find the index of the frame with the given name
*

View file

@ -30,6 +30,8 @@ class FOBJModel : public FModel
{
private:
const char *newSideSep = "$"; // OBJ side separator is /, which is parsed as a line comment by FScanner if two of them are next to each other.
bool hasMissingNormals;
bool hasSmoothGroups;
enum class FaceElement
{
@ -38,6 +40,14 @@ private:
VNormalIndex
};
struct OBJTriRef
{
unsigned int surf;
unsigned int tri;
OBJTriRef(): surf(0), tri(0) {}
OBJTriRef(unsigned int surf, unsigned int tri): surf(surf), tri(tri) {}
bool operator== (OBJTriRef other) { return surf == other.surf && tri == other.tri; }
};
struct OBJFaceSide
{
int vertref;
@ -47,7 +57,9 @@ private:
struct OBJFace
{
unsigned int sideCount;
unsigned int smoothGroup;
OBJFaceSide sides[4];
OBJFace(): sideCount(0), smoothGroup(0) {}
};
struct OBJSurface // 1 surface per 'usemtl'
{
@ -66,16 +78,21 @@ private:
TArray<OBJFace> faces;
TArray<OBJSurface> surfaces;
FScanner sc;
TArray<OBJTriRef>* vertFaces;
int ResolveIndex(int origIndex, FaceElement el);
template<typename T, size_t L> void ParseVector(TArray<T> &array);
bool ParseFaceSide(const FString &side, OBJFace &face, int sidx);
void ConstructSurfaceTris(OBJSurface &surf);
int ResolveIndex(int origIndex, FaceElement el);
void AddVertFaces();
void TriangulateQuad(const OBJFace &quad, OBJFace *tris);
FVector3 RealignVector(FVector3 vecToRealign);
FVector2 FixUV(FVector2 vecToRealign);
FVector3 CalculateNormalFlat(unsigned int surfIdx, unsigned int triIdx);
FVector3 CalculateNormalFlat(OBJTriRef otr);
FVector3 CalculateNormalSmooth(unsigned int vidx, unsigned int smoothGroup);
public:
FOBJModel() {}
FOBJModel(): hasMissingNormals(false), hasSmoothGroups(false), vertFaces(nullptr) {}
~FOBJModel();
bool Load(const char* fn, int lumpnum, const char* buffer, int length) override;
int FindFrame(const char* name) override;

View file

@ -123,11 +123,14 @@ void FUE1Model::LoadGeometry()
// unpack coords
for ( int j=0; j<3; j++ )
Poly.C[j] = FVector2(dpolys[i].uv[j][0]/255.f,dpolys[i].uv[j][1]/255.f);
// compute facet normal
// compute facet normals
for ( int j=0; j<numFrames; j++ )
{
FVector3 dir[2];
dir[0] = verts[Poly.V[1]].Pos-verts[Poly.V[0]].Pos;
dir[1] = verts[Poly.V[2]].Pos-verts[Poly.V[0]].Pos;
Poly.Normal = dir[0]^dir[1];
dir[0] = verts[Poly.V[1]+numVerts*j].Pos-verts[Poly.V[0]+numVerts*j].Pos;
dir[1] = verts[Poly.V[2]+numVerts*j].Pos-verts[Poly.V[0]+numVerts*j].Pos;
Poly.Normals.Push((dir[0]^dir[1]).Unit());
}
// push
polys.Push(Poly);
}
@ -142,7 +145,7 @@ void FUE1Model::LoadGeometry()
for ( int k=0; k<numPolys; k++ )
{
if ( (polys[k].V[0] != j) && (polys[k].V[1] != j) && (polys[k].V[2] != j) ) continue;
nsum += polys[k].Normal;
nsum += polys[k].Normals[i];
}
verts[j+numVerts*i].Normal = nsum.Unit();
}
@ -198,6 +201,8 @@ void FUE1Model::UnloadGeometry()
numPolys = 0;
numGroups = 0;
verts.Reset();
for ( int i=0; i<numPolys; i++ )
polys[i].Normals.Reset();
polys.Reset();
for ( int i=0; i<numGroups; i++ )
groups[i].P.Reset();
@ -275,9 +280,9 @@ void FUE1Model::BuildVertexBuffer( FModelRenderer *renderer )
vert->Set(V.Pos.X,V.Pos.Y,V.Pos.Z,C.X,C.Y);
if ( groups[j].type&PT_Curvy ) // use facet normal
{
vert->SetNormal(polys[groups[j].P[k]].Normal.X,
polys[groups[j].P[k]].Normal.Y,
polys[groups[j].P[k]].Normal.Z);
vert->SetNormal(polys[groups[j].P[k]].Normals[i].X,
polys[groups[j].P[k]].Normals[i].Y,
polys[groups[j].P[k]].Normals[i].Z);
}
else vert->SetNormal(V.Normal.X,V.Normal.Y,V.Normal.Z);
}

View file

@ -89,7 +89,7 @@ private:
{
int V[3];
FVector2 C[3];
FVector3 Normal;
TArray<FVector3> Normals;
};
struct UE1Group
{

View file

@ -10291,9 +10291,14 @@ FxWhileLoop::~FxWhileLoop()
FxExpression *FxWhileLoop::DoResolve(FCompileContext &ctx)
{
CHECKRESOLVED();
SAFE_RESOLVE(Condition, ctx);
SAFE_RESOLVE_OPT(Condition, ctx);
SAFE_RESOLVE_OPT(Code, ctx);
if (Condition == nullptr)
{
Condition = new FxConstant(true, ScriptPosition);
}
if (Condition->ValueType != TypeBool)
{
Condition = new FxBoolCast(Condition);

File diff suppressed because it is too large Load diff

View file

@ -26,6 +26,7 @@
#include <string.h>
#include <stdint.h>
#include <cstring>
#pragma pack(push, 1)
#define ADLDATA_BYTE_COMPARABLE(T) \
@ -34,32 +35,32 @@
inline bool operator!=(const T &a, const T &b) \
{ return !operator==(a, b); }
extern const struct adldata
struct adldata
{
uint32_t modulator_E862, carrier_E862; // See below
uint8_t modulator_40, carrier_40; // KSL/attenuation settings
uint8_t feedconn; // Feedback/connection bits for the channel
int8_t finetune;
} adl[];
};
ADLDATA_BYTE_COMPARABLE(struct adldata)
enum { adlDefaultNumber = 189 };
extern const struct adlinsdata
struct adlinsdata
{
enum { Flag_Pseudo4op = 0x01, Flag_NoSound = 0x02, Flag_Real4op = 0x04 };
enum { Flag_RM_BassDrum = 0x08, Flag_RM_Snare = 0x10, Flag_RM_TomTom = 0x18,
Flag_RM_Cymbal = 0x20, Flag_RM_HiHat = 0x28, Mask_RhythmMode = 0x38 };
uint16_t adlno1, adlno2;
uint8_t tone;
uint8_t flags;
uint16_t ms_sound_kon; // Number of milliseconds it produces sound;
uint16_t ms_sound_koff;
int8_t midi_velocity_offset;
double voice2_fine_tune;
} adlins[];
};
ADLDATA_BYTE_COMPARABLE(struct adlinsdata)
int maxAdlBanks();
extern const unsigned short banks[][256];
extern const char* const banknames[];
enum { adlNoteOnMaxTime = 40000 };
@ -73,9 +74,9 @@ struct adlinsdata2
uint8_t flags;
uint16_t ms_sound_kon; // Number of milliseconds it produces sound;
uint16_t ms_sound_koff;
int8_t midi_velocity_offset;
double voice2_fine_tune;
adlinsdata2() {}
explicit adlinsdata2(const adlinsdata &d);
static adlinsdata2 from_adldata(const adlinsdata &d);
};
ADLDATA_BYTE_COMPARABLE(struct adlinsdata2)
@ -85,25 +86,43 @@ ADLDATA_BYTE_COMPARABLE(struct adlinsdata2)
/**
* @brief Bank global setup
*/
extern const struct AdlBankSetup
struct AdlBankSetup
{
int volumeModel;
bool deepTremolo;
bool deepVibrato;
bool adLibPercussions;
bool scaleModulators;
} adlbanksetup[];
};
#ifndef DISABLE_EMBEDDED_BANKS
int maxAdlBanks();
extern const adldata adl[];
extern const adlinsdata adlins[];
extern const unsigned short banks[][256];
extern const char* const banknames[];
extern const AdlBankSetup adlbanksetup[];
#endif
/**
* @brief Conversion of storage formats
*/
inline adlinsdata2::adlinsdata2(const adlinsdata &d)
: tone(d.tone), flags(d.flags),
ms_sound_kon(d.ms_sound_kon), ms_sound_koff(d.ms_sound_koff),
voice2_fine_tune(d.voice2_fine_tune)
inline adlinsdata2 adlinsdata2::from_adldata(const adlinsdata &d)
{
adl[0] = ::adl[d.adlno1];
adl[1] = ::adl[d.adlno2];
adlinsdata2 ins;
ins.tone = d.tone;
ins.flags = d.flags;
ins.ms_sound_kon = d.ms_sound_kon;
ins.ms_sound_koff = d.ms_sound_koff;
ins.midi_velocity_offset = d.midi_velocity_offset;
ins.voice2_fine_tune = d.voice2_fine_tune;
#ifdef DISABLE_EMBEDDED_BANKS
std::memset(ins.adl, 0, sizeof(adldata) * 2);
#else
ins.adl[0] = ::adl[d.adlno1];
ins.adl[1] = ::adl[d.adlno2];
#endif
return ins;
}
/**

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -24,9 +24,11 @@
#ifndef ADLMIDI_HPP
#define ADLMIDI_HPP
#include "adlmidi.h"
struct ADL_MIDIPlayer;
class AdlInstrumentTester
class ADLMIDI_DECLSPEC AdlInstrumentTester
{
struct Impl;
Impl *P;

View file

@ -40,7 +40,7 @@ template <class T>
class BasicBankMap
{
public:
typedef uint16_t key_type; /* the bank identifier */
typedef size_t key_type; /* the bank identifier */
typedef T mapped_type;
typedef std::pair<key_type, T> value_type;
@ -74,7 +74,7 @@ private:
enum
{
hash_bits = 8, /* worst case # of collisions: 128^2/2^hash_bits */
hash_buckets = 1 << hash_bits,
hash_buckets = 1 << hash_bits
};
public:

View file

@ -37,7 +37,7 @@ template <class T>
inline size_t BasicBankMap<T>::hash(key_type key)
{
// disregard the 0 high bit in LSB
key = (key & 127) | ((key >> 8) << 7);
key = key_type(key & 127) | key_type((key >> 8) << 7);
// take low part as hash value
return key & (hash_buckets - 1);
}

View file

@ -0,0 +1,124 @@
/*
* 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) 2015-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 "adldata.hh"
#include "wopl/wopl_file.h"
#include <cmath>
template <class WOPLI>
static void cvt_generic_to_FMIns(adlinsdata2 &ins, const WOPLI &in)
{
ins.voice2_fine_tune = 0.0;
int8_t voice2_fine_tune = in.second_voice_detune;
if(voice2_fine_tune != 0)
{
if(voice2_fine_tune == 1)
ins.voice2_fine_tune = 0.000025;
else if(voice2_fine_tune == -1)
ins.voice2_fine_tune = -0.000025;
else
ins.voice2_fine_tune = voice2_fine_tune * (15.625 / 1000.0);
}
ins.midi_velocity_offset = in.midi_velocity_offset;
ins.tone = in.percussion_key_number;
ins.flags = (in.inst_flags & WOPL_Ins_4op) && (in.inst_flags & WOPL_Ins_Pseudo4op) ? adlinsdata::Flag_Pseudo4op : 0;
ins.flags|= (in.inst_flags & WOPL_Ins_4op) && ((in.inst_flags & WOPL_Ins_Pseudo4op) == 0) ? adlinsdata::Flag_Real4op : 0;
ins.flags|= (in.inst_flags & WOPL_Ins_IsBlank) ? adlinsdata::Flag_NoSound : 0;
ins.flags|= in.inst_flags & WOPL_RhythmModeMask;
for(size_t op = 0, slt = 0; op < 4; op++, slt++)
{
ins.adl[slt].carrier_E862 =
((static_cast<uint32_t>(in.operators[op].waveform_E0) << 24) & 0xFF000000) //WaveForm
| ((static_cast<uint32_t>(in.operators[op].susrel_80) << 16) & 0x00FF0000) //SusRel
| ((static_cast<uint32_t>(in.operators[op].atdec_60) << 8) & 0x0000FF00) //AtDec
| ((static_cast<uint32_t>(in.operators[op].avekf_20) << 0) & 0x000000FF); //AVEKM
ins.adl[slt].carrier_40 = in.operators[op].ksl_l_40;//KSLL
op++;
ins.adl[slt].modulator_E862 =
((static_cast<uint32_t>(in.operators[op].waveform_E0) << 24) & 0xFF000000) //WaveForm
| ((static_cast<uint32_t>(in.operators[op].susrel_80) << 16) & 0x00FF0000) //SusRel
| ((static_cast<uint32_t>(in.operators[op].atdec_60) << 8) & 0x0000FF00) //AtDec
| ((static_cast<uint32_t>(in.operators[op].avekf_20) << 0) & 0x000000FF); //AVEKM
ins.adl[slt].modulator_40 = in.operators[op].ksl_l_40;//KSLL
}
ins.adl[0].finetune = static_cast<int8_t>(in.note_offset1);
ins.adl[0].feedconn = in.fb_conn1_C0;
ins.adl[1].finetune = static_cast<int8_t>(in.note_offset2);
ins.adl[1].feedconn = in.fb_conn2_C0;
ins.ms_sound_kon = in.delay_on_ms;
ins.ms_sound_koff = in.delay_off_ms;
}
template <class WOPLI>
static void cvt_FMIns_to_generic(WOPLI &ins, const adlinsdata2 &in)
{
ins.second_voice_detune = 0;
double voice2_fine_tune = in.voice2_fine_tune;
if(voice2_fine_tune != 0)
{
if(voice2_fine_tune > 0 && voice2_fine_tune <= 0.000025)
ins.second_voice_detune = 1;
else if(voice2_fine_tune < 0 && voice2_fine_tune >= -0.000025)
ins.second_voice_detune = -1;
else
{
long value = static_cast<long>(round(voice2_fine_tune * (1000.0 / 15.625)));
value = (value < -128) ? -128 : value;
value = (value > +127) ? +127 : value;
ins.second_voice_detune = static_cast<int8_t>(value);
}
}
ins.midi_velocity_offset = in.midi_velocity_offset;
ins.percussion_key_number = in.tone;
ins.inst_flags = (in.flags & (adlinsdata::Flag_Pseudo4op|adlinsdata::Flag_Real4op)) ? WOPL_Ins_4op : 0;
ins.inst_flags|= (in.flags & adlinsdata::Flag_Pseudo4op) ? WOPL_Ins_Pseudo4op : 0;
ins.inst_flags|= (in.flags & adlinsdata::Flag_NoSound) ? WOPL_Ins_IsBlank : 0;
ins.inst_flags |= in.flags & adlinsdata::Mask_RhythmMode;
for(size_t op = 0; op < 4; op++)
{
const adldata &in2op = in.adl[(op < 2) ? 0 : 1];
uint32_t regE862 = ((op & 1) == 0) ? in2op.carrier_E862 : in2op.modulator_E862;
uint8_t reg40 = ((op & 1) == 0) ? in2op.carrier_40 : in2op.modulator_40;
ins.operators[op].waveform_E0 = static_cast<uint8_t>(regE862 >> 24);
ins.operators[op].susrel_80 = static_cast<uint8_t>(regE862 >> 16);
ins.operators[op].atdec_60 = static_cast<uint8_t>(regE862 >> 8);
ins.operators[op].avekf_20 = static_cast<uint8_t>(regE862 >> 0);
ins.operators[op].ksl_l_40 = reg40;
}
ins.note_offset1 = in.adl[0].finetune;
ins.fb_conn1_C0 = in.adl[0].feedconn;
ins.note_offset2 = in.adl[1].finetune;
ins.fb_conn2_C0 = in.adl[1].feedconn;
ins.delay_on_ms = in.ms_sound_kon;
ins.delay_off_ms = in.ms_sound_koff;
}

View file

@ -1,4 +1,4 @@
/*
/*
* libADLMIDI is a free MIDI to WAV conversion library with OPL3 emulation
*
* Original ADLMIDI code: Copyright (c) 2010-2014 Joel Yliluoma <bisqwit@iki.fi>
@ -22,168 +22,23 @@
*/
#include "adlmidi_private.hpp"
#include "adlmidi_cvt.hpp"
#include "wopl/wopl_file.h"
#ifndef ADLMIDI_DISABLE_MIDI_SEQUENCER
# ifndef ADLMIDI_DISABLE_MUS_SUPPORT
# include "adlmidi_mus2mid.h"
# endif//MUS
# ifndef ADLMIDI_DISABLE_XMI_SUPPORT
# include "adlmidi_xmi2mid.h"
# endif//XMI
#endif //ADLMIDI_DISABLE_MIDI_SEQUENCER
#ifndef ADLMIDI_DISABLE_MIDI_SEQUENCER
uint64_t MIDIplay::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 MIDIplay::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;
}
#endif
bool MIDIplay::LoadBank(const std::string &filename)
{
fileReader file;
FileAndMemReader file;
file.openFile(filename.c_str());
return LoadBank(file);
}
bool MIDIplay::LoadBank(const void *data, size_t size)
{
fileReader file;
FileAndMemReader file;
file.openData(data, size);
return LoadBank(file);
}
template <class WOPLI>
static void cvt_generic_to_FMIns(adlinsdata2 &ins, const WOPLI &in)
{
ins.voice2_fine_tune = 0.0;
int8_t voice2_fine_tune = in.second_voice_detune;
if(voice2_fine_tune != 0)
{
if(voice2_fine_tune == 1)
ins.voice2_fine_tune = 0.000025;
else if(voice2_fine_tune == -1)
ins.voice2_fine_tune = -0.000025;
else
ins.voice2_fine_tune = voice2_fine_tune * (15.625 / 1000.0);
}
ins.tone = in.percussion_key_number;
ins.flags = (in.inst_flags & WOPL_Ins_4op) && (in.inst_flags & WOPL_Ins_Pseudo4op) ? adlinsdata::Flag_Pseudo4op : 0;
ins.flags|= (in.inst_flags & WOPL_Ins_4op) && ((in.inst_flags & WOPL_Ins_Pseudo4op) == 0) ? adlinsdata::Flag_Real4op : 0;
ins.flags|= (in.inst_flags & WOPL_Ins_IsBlank) ? adlinsdata::Flag_NoSound : 0;
bool fourOps = (in.inst_flags & WOPL_Ins_4op) || (in.inst_flags & WOPL_Ins_Pseudo4op);
for(size_t op = 0, slt = 0; op < static_cast<size_t>(fourOps ? 4 : 2); op++, slt++)
{
ins.adl[slt].carrier_E862 =
((static_cast<uint32_t>(in.operators[op].waveform_E0) << 24) & 0xFF000000) //WaveForm
| ((static_cast<uint32_t>(in.operators[op].susrel_80) << 16) & 0x00FF0000) //SusRel
| ((static_cast<uint32_t>(in.operators[op].atdec_60) << 8) & 0x0000FF00) //AtDec
| ((static_cast<uint32_t>(in.operators[op].avekf_20) << 0) & 0x000000FF); //AVEKM
ins.adl[slt].carrier_40 = in.operators[op].ksl_l_40;//KSLL
op++;
ins.adl[slt].modulator_E862 =
((static_cast<uint32_t>(in.operators[op].waveform_E0) << 24) & 0xFF000000) //WaveForm
| ((static_cast<uint32_t>(in.operators[op].susrel_80) << 16) & 0x00FF0000) //SusRel
| ((static_cast<uint32_t>(in.operators[op].atdec_60) << 8) & 0x0000FF00) //AtDec
| ((static_cast<uint32_t>(in.operators[op].avekf_20) << 0) & 0x000000FF); //AVEKM
ins.adl[slt].modulator_40 = in.operators[op].ksl_l_40;//KSLL
}
ins.adl[0].finetune = static_cast<int8_t>(in.note_offset1);
ins.adl[0].feedconn = in.fb_conn1_C0;
if(!fourOps)
ins.adl[1] = ins.adl[0];
else
{
ins.adl[1].finetune = static_cast<int8_t>(in.note_offset2);
ins.adl[1].feedconn = in.fb_conn2_C0;
}
ins.ms_sound_kon = in.delay_on_ms;
ins.ms_sound_koff = in.delay_off_ms;
}
template <class WOPLI>
static void cvt_FMIns_to_generic(WOPLI &ins, const adlinsdata2 &in)
{
ins.second_voice_detune = 0;
double voice2_fine_tune = in.voice2_fine_tune;
if(voice2_fine_tune != 0)
{
if(voice2_fine_tune > 0 && voice2_fine_tune <= 0.000025)
ins.second_voice_detune = 1;
else if(voice2_fine_tune < 0 && voice2_fine_tune >= -0.000025)
ins.second_voice_detune = -1;
else
{
long value = lround(voice2_fine_tune * (1000.0 / 15.625));
value = (value < -128) ? -128 : value;
value = (value > +127) ? +127 : value;
ins.second_voice_detune = static_cast<int8_t>(value);
}
}
ins.percussion_key_number = in.tone;
bool fourOps = (in.flags & adlinsdata::Flag_Pseudo4op) || in.adl[0] != in.adl[1];
ins.inst_flags = fourOps ? WOPL_Ins_4op : 0;
ins.inst_flags|= (in.flags & adlinsdata::Flag_Pseudo4op) ? WOPL_Ins_Pseudo4op : 0;
ins.inst_flags|= (in.flags & adlinsdata::Flag_NoSound) ? WOPL_Ins_IsBlank : 0;
for(size_t op = 0, slt = 0; op < static_cast<size_t>(fourOps ? 4 : 2); op++, slt++)
{
ins.operators[op].waveform_E0 = static_cast<uint8_t>(in.adl[slt].carrier_E862 >> 24);
ins.operators[op].susrel_80 = static_cast<uint8_t>(in.adl[slt].carrier_E862 >> 16);
ins.operators[op].atdec_60 = static_cast<uint8_t>(in.adl[slt].carrier_E862 >> 8);
ins.operators[op].avekf_20 = static_cast<uint8_t>(in.adl[slt].carrier_E862 >> 0);
ins.operators[op].ksl_l_40 = in.adl[slt].carrier_40;
op++;
ins.operators[op].waveform_E0 = static_cast<uint8_t>(in.adl[slt].carrier_E862 >> 24);
ins.operators[op].susrel_80 = static_cast<uint8_t>(in.adl[slt].carrier_E862 >> 16);
ins.operators[op].atdec_60 = static_cast<uint8_t>(in.adl[slt].carrier_E862 >> 8);
ins.operators[op].avekf_20 = static_cast<uint8_t>(in.adl[slt].carrier_E862 >> 0);
ins.operators[op].ksl_l_40 = in.adl[slt].carrier_40;
}
ins.note_offset1 = in.adl[0].finetune;
ins.fb_conn1_C0 = in.adl[0].feedconn;
if(!fourOps)
{
ins.operators[2] = ins.operators[0];
ins.operators[3] = ins.operators[1];
}
else
{
ins.note_offset2 = in.adl[1].finetune;
ins.fb_conn2_C0 = in.adl[1].feedconn;
}
ins.delay_on_ms = in.ms_sound_kon;
ins.delay_off_ms = in.ms_sound_koff;
}
void cvt_ADLI_to_FMIns(adlinsdata2 &ins, const ADL_Instrument &in)
{
return cvt_generic_to_FMIns(ins, in);
@ -194,7 +49,7 @@ void cvt_FMIns_to_ADLI(ADL_Instrument &ins, const adlinsdata2 &in)
cvt_FMIns_to_generic(ins, in);
}
bool MIDIplay::LoadBank(MIDIplay::fileReader &fr)
bool MIDIplay::LoadBank(FileAndMemReader &fr)
{
int err = 0;
WOPLFile *wopl = NULL;
@ -207,9 +62,8 @@ bool MIDIplay::LoadBank(MIDIplay::fileReader &fr)
}
// Read complete bank file into the memory
fr.seek(0, SEEK_END);
fsize = fr.tell();
fr.seek(0, SEEK_SET);
fsize = fr.fileSize();
fr.seek(0, FileAndMemReader::SET);
// Allocate necessary memory block
raw_file_data = (char*)malloc(fsize);
if(!raw_file_data)
@ -250,29 +104,28 @@ bool MIDIplay::LoadBank(MIDIplay::fileReader &fr)
}
}
opl.dynamic_bank_setup.adLibPercussions = false;
opl.dynamic_bank_setup.scaleModulators = false;
opl.dynamic_bank_setup.deepTremolo = (wopl->opl_flags & WOPL_FLAG_DEEP_TREMOLO) != 0;
opl.dynamic_bank_setup.deepVibrato = (wopl->opl_flags & WOPL_FLAG_DEEP_VIBRATO) != 0;
opl.dynamic_bank_setup.volumeModel = wopl->volume_model;
m_setup.HighTremoloMode = -1;
m_setup.HighVibratoMode = -1;
m_setup.VolumeModel = ADLMIDI_VolumeModel_AUTO;
m_synth.m_insBankSetup.adLibPercussions = false;
m_synth.m_insBankSetup.scaleModulators = false;
m_synth.m_insBankSetup.deepTremolo = (wopl->opl_flags & WOPL_FLAG_DEEP_TREMOLO) != 0;
m_synth.m_insBankSetup.deepVibrato = (wopl->opl_flags & WOPL_FLAG_DEEP_VIBRATO) != 0;
m_synth.m_insBankSetup.volumeModel = wopl->volume_model;
m_setup.deepTremoloMode = -1;
m_setup.deepVibratoMode = -1;
m_setup.volumeScaleModel = ADLMIDI_VolumeModel_AUTO;
opl.setEmbeddedBank(m_setup.AdlBank);
m_synth.setEmbeddedBank(m_setup.bankId);
uint16_t slots_counts[2] = {wopl->banks_count_melodic, wopl->banks_count_percussion};
WOPLBank *slots_src_ins[2] = { wopl->banks_melodic, wopl->banks_percussive };
for(unsigned ss = 0; ss < 2; ss++)
for(size_t ss = 0; ss < 2; ss++)
{
for(unsigned i = 0; i < slots_counts[ss]; i++)
for(size_t i = 0; i < slots_counts[ss]; i++)
{
unsigned bankno =
(slots_src_ins[ss][i].bank_midi_msb * 256) +
slots_src_ins[ss][i].bank_midi_lsb +
(ss ? OPL3::PercussionTag : 0);
OPL3::Bank &bank = opl.dynamic_banks[bankno];
size_t bankno = (slots_src_ins[ss][i].bank_midi_msb * 256) +
(slots_src_ins[ss][i].bank_midi_lsb) +
(ss ? size_t(OPL3::PercussionTag) : 0);
OPL3::Bank &bank = m_synth.m_insBanks[bankno];
for(int j = 0; j < 128; j++)
{
adlinsdata2 &ins = bank.ins[j];
@ -283,7 +136,7 @@ bool MIDIplay::LoadBank(MIDIplay::fileReader &fr)
}
}
opl.AdlBank = ~0u; // Use dynamic banks!
m_synth.m_embeddedBank = OPL3::CustomBankTag; // Use dynamic banks!
//Percussion offset is count of instruments multipled to count of melodic banks
applySetup();
@ -293,189 +146,45 @@ bool MIDIplay::LoadBank(MIDIplay::fileReader &fr)
}
#ifndef ADLMIDI_DISABLE_MIDI_SEQUENCER
bool MIDIplay::LoadMIDI(const std::string &filename)
{
fileReader file;
file.openFile(filename.c_str());
if(!LoadMIDI(file))
return false;
return true;
}
bool MIDIplay::LoadMIDI(const void *data, size_t size)
bool MIDIplay::LoadMIDI_pre()
{
fileReader file;
file.openData(data, size);
return LoadMIDI(file);
}
bool MIDIplay::LoadMIDI(MIDIplay::fileReader &fr)
{
size_t fsize;
ADL_UNUSED(fsize);
//! Temp buffer for conversion
AdlMIDI_CPtr<uint8_t> cvt_buf;
errorString.clear();
#ifdef DISABLE_EMBEDDED_BANKS
if((opl.AdlBank != ~0u) || opl.dynamic_banks.empty())
#ifdef DISABLE_EMBEDDED_BANKS
if((m_synth.m_embeddedBank != OPL3::CustomBankTag) || m_synth.m_insBanks.empty())
{
errorStringOut = "Bank is not set! Please load any instruments bank by using of adl_openBankFile() or adl_openBankData() functions!";
return false;
}
#endif
if(!fr.isValid())
{
errorStringOut = "Invalid data stream!\n";
#ifndef _WIN32
errorStringOut += std::strerror(errno);
#endif
return false;
}
#endif
/**** Set all properties BEFORE starting of actial file reading! ****/
resetMIDI();
applySetup();
atEnd = false;
loopStart = true;
invalidLoop = false;
return true;
}
bool is_GMF = false; // GMD/MUS files (ScummVM)
//bool is_MUS = false; // MUS/DMX files (Doom)
bool is_IMF = false; // IMF
bool is_CMF = false; // Creative Music format (CMF/CTMF)
bool is_RSXX = false; // RSXX, such as Cartooners
bool MIDIplay::LoadMIDI_post()
{
MidiSequencer::FileFormat format = m_sequencer.getFormat();
if(format == MidiSequencer::Format_CMF)
{
const std::vector<MidiSequencer::CmfInstrument> &instruments = m_sequencer.getRawCmfInstruments();
m_synth.m_insBanks.clear();//Clean up old banks
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)
uint16_t ins_count = static_cast<uint16_t>(instruments.size());
for(uint16_t i = 0; i < ins_count; ++i)
{
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;
}
#ifndef ADLMIDI_DISABLE_MUS_SUPPORT
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 = AdlMidi_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;
}
#endif //ADLMIDI_DISABLE_MUS_SUPPORT
#ifndef ADLMIDI_DISABLE_XMI_SUPPORT
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 = AdlMidi_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;
}
#endif //ADLMIDI_DISABLE_XMI_SUPPORT
else if(std::memcmp(HeaderBuf, "CTMF", 4) == 0)
{
opl.dynamic_banks.clear();
// Creative Music Format (CMF).
// When playing CTMF files, use the following commandline:
// adlmidi song8.ctmf -p -v 1 1 0
// i.e. enable percussion mode, deeper vibrato, and use only 1 card.
is_CMF = true;
//unsigned version = ReadLEint(HeaderBuf+4, 2);
uint64_t ins_start = ReadLEint(HeaderBuf + 6, 2);
uint64_t mus_start = ReadLEint(HeaderBuf + 8, 2);
//unsigned deltas = ReadLEint(HeaderBuf+10, 2);
uint64_t ticks = ReadLEint(HeaderBuf + 12, 2);
// Read title, author, remarks start offsets in file
fr.read(HeaderBuf, 1, 6);
//unsigned long notes_starts[3] = {ReadLEint(HeaderBuf+0,2),ReadLEint(HeaderBuf+0,4),ReadLEint(HeaderBuf+0,6)};
fr.seek(16, SEEK_CUR); // Skip the channels-in-use table
fr.read(HeaderBuf, 1, 4);
uint64_t ins_count = ReadLEint(HeaderBuf + 0, 2); //, basictempo = ReadLEint(HeaderBuf+2, 2);
fr.seek(static_cast<long>(ins_start), SEEK_SET);
//std::printf("%u instruments\n", ins_count);
for(unsigned i = 0; i < ins_count; ++i)
{
unsigned bank = i / 256;
bank = (bank & 127) + ((bank >> 7) << 8);
const uint8_t *InsData = instruments[i].data;
size_t bank = i / 256;
bank = ((bank & 127) + ((bank >> 7) << 8));
if(bank > 127 + (127 << 8))
break;
bank += (i % 256 < 128) ? 0 : OPL3::PercussionTag;
bank += (i % 256 < 128) ? 0 : size_t(OPL3::PercussionTag);
unsigned char InsData[16];
fr.read(InsData, 1, 16);
/*std::printf("Ins %3u: %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X\n",
i, InsData[0],InsData[1],InsData[2],InsData[3], InsData[4],InsData[5],InsData[6],InsData[7],
InsData[8],InsData[9],InsData[10],InsData[11], InsData[12],InsData[13],InsData[14],InsData[15]);*/
adlinsdata2 &adlins = opl.dynamic_banks[bank].ins[i % 128];
adlinsdata2 &adlins = m_synth.m_insBanks[bank].ins[i % 128];
adldata adl;
adl.modulator_E862 =
((static_cast<uint32_t>(InsData[8] & 0x07) << 24) & 0xFF000000) //WaveForm
@ -500,224 +209,79 @@ riffskip:
adlins.voice2_fine_tune = 0.0;
}
fr.seeku(mus_start, SEEK_SET);
TrackCount = 1;
DeltaTicks = (size_t)ticks;
opl.AdlBank = ~0u; // Ignore AdlBank number, use dynamic banks instead
m_synth.m_embeddedBank = OPL3::CustomBankTag; // Ignore AdlBank number, use dynamic banks instead
//std::printf("CMF deltas %u ticks %u, basictempo = %u\n", deltas, ticks, basictempo);
opl.AdlPercussionMode = true;
opl.m_musicMode = OPL3::MODE_CMF;
opl.m_volumeScale = OPL3::VOLUME_NATIVE;
m_synth.m_rhythmMode = true;
m_synth.m_musicMode = OPL3::MODE_CMF;
m_synth.m_volumeScale = OPL3::VOLUME_NATIVE;
m_synth.m_numChips = 1;
m_synth.m_numFourOps = 0;
}
else
else if(format == MidiSequencer::Format_RSXX)
{
// 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;
//opl.CartoonersVolumes = true;
opl.m_musicMode = OPL3::MODE_RSXX;
opl.m_volumeScale = OPL3::VOLUME_NATIVE;
m_synth.m_musicMode = OPL3::MODE_RSXX;
m_synth.m_volumeScale = OPL3::VOLUME_NATIVE;
m_synth.m_numChips = 1;
m_synth.m_numFourOps = 0;
}
}
// Try parsing as an IMF file
if(!is_RSXX)
else if(format == MidiSequencer::Format_IMF)
{
do
{
uint8_t raw[4];
size_t end = static_cast<size_t>(HeaderBuf[0]) + 256 * static_cast<size_t>(HeaderBuf[1]);
if(!end || (end & 3))
break;
size_t backup_pos = fr.tell();
int64_t sum1 = 0, sum2 = 0;
fr.seek(2, SEEK_SET);
for(unsigned n = 0; n < 42; ++n)
{
if(fr.read(raw, 1, 4) != 4)
break;
int64_t value1 = raw[0];
value1 += raw[1] << 8;
sum1 += value1;
int64_t value2 = raw[2];
value2 += raw[3] << 8;
sum2 += value2;
}
fr.seek(static_cast<long>(backup_pos), SEEK_SET);
if(sum1 > sum2)
{
is_IMF = true;
DeltaTicks = 1;
}
} while(false);
}
if(!is_IMF && !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>());
InvDeltaTicks = fraction<uint64_t>(1, 1000000l * static_cast<uint64_t>(DeltaTicks));
if(is_CMF || is_RSXX)
Tempo = fraction<uint64_t>(1, static_cast<uint64_t>(DeltaTicks));
else
Tempo = fraction<uint64_t>(1, static_cast<uint64_t>(DeltaTicks) * 2);
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_IMF)
{
//std::fprintf(stderr, "Reading IMF file...\n");
size_t end = static_cast<size_t>(HeaderBuf[0]) + 256 * static_cast<size_t>(HeaderBuf[1]);
unsigned IMF_tempo = 1428;
static const unsigned char imf_tempo[] = {0x0,//Zero delay!
MidiEvent::T_SPECIAL, MidiEvent::ST_TEMPOCHANGE, 0x4,
static_cast<uint8_t>(IMF_tempo >> 24),
static_cast<uint8_t>(IMF_tempo >> 16),
static_cast<uint8_t>(IMF_tempo >> 8),
static_cast<uint8_t>(IMF_tempo)
};
TrackData[tk].insert(TrackData[tk].end(), imf_tempo, imf_tempo + sizeof(imf_tempo));
TrackData[tk].push_back(0x00);
fr.seek(2, SEEK_SET);
while(fr.tell() < end && !fr.eof())
{
uint8_t special_event_buf[5];
uint8_t raw[4];
special_event_buf[0] = MidiEvent::T_SPECIAL;
special_event_buf[1] = MidiEvent::ST_RAWOPL;
special_event_buf[2] = 0x02;
if(fr.read(raw, 1, 4) != 4)
break;
special_event_buf[3] = raw[0]; // port index
special_event_buf[4] = raw[1]; // port value
uint32_t delay = static_cast<uint32_t>(raw[2]);
delay += 256 * static_cast<uint32_t>(raw[3]);
totalGotten += 4;
//if(special_event_buf[3] <= 8) continue;
//fprintf(stderr, "Put %02X <- %02X, plus %04X delay\n", special_event_buf[3],special_event_buf[4], delay);
TrackData[tk].insert(TrackData[tk].end(), special_event_buf, special_event_buf + 5);
//if(delay>>21) TrackData[tk].push_back( 0x80 | ((delay>>21) & 0x7F ) );
if(delay >> 14)
TrackData[tk].push_back(0x80 | ((delay >> 14) & 0x7F));
if(delay >> 7)
TrackData[tk].push_back(0x80 | ((delay >> 7) & 0x7F));
TrackData[tk].push_back(((delay >> 0) & 0x7F));
}
TrackData[tk].insert(TrackData[tk].end(), EndTag + 0, EndTag + 4);
//CurrentPosition.track[tk].delay = 0;
//CurrentPosition.began = true;
//std::fprintf(stderr, "Done reading IMF file\n");
opl.NumFourOps = 0; //Don't use 4-operator channels for IMF playing!
opl.m_musicMode = OPL3::MODE_IMF;
m_synth.m_numFourOps = 0; //Don't use 4-operator channels for IMF playing!
m_synth.m_musicMode = OPL3::MODE_IMF;
m_synth.m_numChips = 1;
m_synth.m_numFourOps = 0;
}
else
{
// Take the rest of the file
if(is_GMF || is_CMF || is_RSXX)
{
size_t pos = fr.tell();
fr.seek(0, SEEK_END);
TrackLength = fr.tell() - pos;
fr.seek(static_cast<long>(pos), SEEK_SET);
}
//else if(is_MUS) // Read TrackLength from file position 4
//{
// size_t pos = fr.tell();
// fr.seek(4, SEEK_SET);
// TrackLength = static_cast<size_t>(fr.getc());
// TrackLength += static_cast<size_t>(fr.getc() << 8);
// 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);
m_synth.m_numChips = m_setup.numChips;
if(m_setup.numFourOps < 0)
adlCalculateFourOpChannels(this, true);
}
// 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 << ")";
// ADLMIDI_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
if(!buildTrackData())
{
errorStringOut = fr._fileName + ": MIDI data parsing error has occouped!\n" + errorString;
return false;
}
opl.Reset(m_setup.emulator, m_setup.PCM_RATE, this); // Reset OPL3 chip
m_setup.tick_skip_samples_delay = 0;
m_synth.reset(m_setup.emulator, m_setup.PCM_RATE, this); // Reset OPL3 chip
//opl.Reset(); // ...twice (just in case someone misprogrammed OPL3 previously)
ch.clear();
ch.resize(opl.NumChannels);
m_chipChannels.clear();
m_chipChannels.resize(m_synth.m_numChannels);
return true;
}
#endif //ADLMIDI_DISABLE_MIDI_SEQUENCER
bool MIDIplay::LoadMIDI(const std::string &filename)
{
FileAndMemReader file;
file.openFile(filename.c_str());
if(!LoadMIDI_pre())
return false;
if(!m_sequencer.loadMIDI(file))
{
errorStringOut = m_sequencer.getErrorString();
return false;
}
if(!LoadMIDI_post())
return false;
return true;
}
bool MIDIplay::LoadMIDI(const void *data, size_t size)
{
FileAndMemReader file;
file.openData(data, size);
if(!LoadMIDI_pre())
return false;
if(!m_sequencer.loadMIDI(file))
{
errorStringOut = m_sequencer.getErrorString();
return false;
}
if(!LoadMIDI_post())
return false;
return true;
}
#endif /* ADLMIDI_DISABLE_MIDI_SEQUENCER */

File diff suppressed because it is too large Load diff

View file

@ -22,6 +22,8 @@
*/
#include "adlmidi_private.hpp"
#include <stdlib.h>
#include <cassert>
#ifdef ADLMIDI_HW_OPL
static const unsigned OPLBase = 0x388;
@ -42,32 +44,48 @@ static const unsigned OPLBase = 0x388;
# endif
#endif
#ifdef DISABLE_EMBEDDED_BANKS
/*
Dummy data which replaces adldata.cpp banks database
*/
static const unsigned adl_emulatorSupport = 0
#ifndef ADLMIDI_HW_OPL
# ifndef ADLMIDI_DISABLE_NUKED_EMULATOR
| (1u << ADLMIDI_EMU_NUKED) | (1u << ADLMIDI_EMU_NUKED_174)
# endif
const struct adldata adl[] =
{
{0, 0, (unsigned char)'\0', (unsigned char)'\0', (unsigned char)'\0', 0}
};
# ifndef ADLMIDI_DISABLE_DOSBOX_EMULATOR
| (1u << ADLMIDI_EMU_DOSBOX)
# endif
#endif
;
const struct adlinsdata adlins[] =
//! Check emulator availability
bool adl_isEmulatorAvailable(int emulator)
{
{0, 0, 0, 0, 0, 0, 0.0}
};
int maxAdlBanks()
{
return 0;
return (adl_emulatorSupport & (1u << (unsigned)emulator)) != 0;
}
const unsigned short banks[][256] = {{0}};
const char *const banknames[] = {"<Embedded banks are disabled>"};
const AdlBankSetup adlbanksetup[] = {{0, 1, 1, 0, 0}};
#endif
//! Find highest emulator
int adl_getHighestEmulator()
{
int emu = -1;
for(unsigned m = adl_emulatorSupport; m > 0; m >>= 1)
++emu;
return emu;
}
static const unsigned short Operators[23 * 2] =
//! Find lowest emulator
int adl_getLowestEmulator()
{
int emu = -1;
unsigned m = adl_emulatorSupport;
if(m > 0)
{
for(emu = 0; (m & 1) == 0; m >>= 1)
++emu;
}
return emu;
}
//! Per-channel and per-operator registers map
static const uint16_t g_operatorsMap[23 * 2] =
{
// Channels 0-2
0x000, 0x003, 0x001, 0x004, 0x002, 0x005, // operators 0, 3, 1, 4, 2, 5
@ -91,7 +109,8 @@ static const unsigned short Operators[23 * 2] =
0x011, 0xFFF
}; // operator 13
static const unsigned short Channels[23] =
//! Channel map to regoster offsets
static const uint16_t g_channelsMap[23] =
{
0x000, 0x001, 0x002, 0x003, 0x004, 0x005, 0x006, 0x007, 0x008, // 0..8
0x100, 0x101, 0x102, 0x103, 0x104, 0x105, 0x106, 0x107, 0x108, // 9..17 (secondary set)
@ -129,29 +148,6 @@ static const unsigned short Channels[23] =
Ports: ???
*/
void OPL3::setEmbeddedBank(unsigned int bank)
{
AdlBank = bank;
//Embedded banks are supports 128:128 GM set only
dynamic_banks.clear();
if(bank >= static_cast<unsigned int>(maxAdlBanks()))
return;
Bank *bank_pair[2] =
{
&dynamic_banks[0],
&dynamic_banks[PercussionTag]
};
for(unsigned i = 0; i < 256; ++i)
{
size_t meta = banks[bank][i];
adlinsdata2 &ins = bank_pair[i / 128]->ins[i % 128];
ins = adlinsdata2(adlins[meta]);
}
}
static adlinsdata2 makeEmptyInstrument()
{
adlinsdata2 ins;
@ -160,126 +156,239 @@ static adlinsdata2 makeEmptyInstrument()
return ins;
}
const adlinsdata2 OPL3::emptyInstrument = makeEmptyInstrument();
const adlinsdata2 OPL3::m_emptyInstrument = makeEmptyInstrument();
OPL3::OPL3() :
NumCards(1),
NumFourOps(0),
HighTremoloMode(false),
HighVibratoMode(false),
AdlPercussionMode(false),
m_numChips(1),
m_numFourOps(0),
m_deepTremoloMode(false),
m_deepVibratoMode(false),
m_rhythmMode(false),
m_softPanning(false),
m_musicMode(MODE_MIDI),
m_volumeScale(VOLUME_Generic)
{
m_insBankSetup.volumeModel = OPL3::VOLUME_Generic;
m_insBankSetup.deepTremolo = false;
m_insBankSetup.deepVibrato = false;
m_insBankSetup.adLibPercussions = false;
m_insBankSetup.scaleModulators = false;
#ifdef DISABLE_EMBEDDED_BANKS
AdlBank = ~0u;
m_embeddedBank = CustomBankTag;
#else
setEmbeddedBank(0);
#endif
}
void OPL3::Poke(size_t card, uint16_t index, uint8_t value)
bool OPL3::setupLocked()
{
#ifdef ADLMIDI_HW_OPL
(void)card;
unsigned o = index >> 8;
return (m_musicMode == MODE_CMF ||
m_musicMode == MODE_IMF ||
m_musicMode == MODE_RSXX);
}
void OPL3::setEmbeddedBank(uint32_t bank)
{
#ifndef DISABLE_EMBEDDED_BANKS
m_embeddedBank = bank;
//Embedded banks are supports 128:128 GM set only
m_insBanks.clear();
if(bank >= static_cast<unsigned int>(maxAdlBanks()))
return;
Bank *bank_pair[2] =
{
&m_insBanks[0],
&m_insBanks[PercussionTag]
};
for(unsigned i = 0; i < 256; ++i)
{
size_t meta = banks[bank][i];
adlinsdata2 &ins = bank_pair[i / 128]->ins[i % 128];
ins = adlinsdata2::from_adldata(::adlins[meta]);
}
#else
ADL_UNUSED(bank);
#endif
}
void OPL3::writeReg(size_t chip, uint16_t address, uint8_t value)
{
#ifdef ADLMIDI_HW_OPL
ADL_UNUSED(chip);
unsigned o = address >> 8;
unsigned port = OPLBase + o * 2;
#ifdef __DJGPP__
outportb(port, index);
outportb(port, address);
for(unsigned c = 0; c < 6; ++c) inportb(port);
outportb(port + 1, value);
for(unsigned c = 0; c < 35; ++c) inportb(port);
#endif
#ifdef __WATCOMC__
outp(port, index);
outp(port, address);
for(uint16_t c = 0; c < 6; ++c) inp(port);
outp(port + 1, value);
for(uint16_t c = 0; c < 35; ++c) inp(port);
#endif//__WATCOMC__
#else
cardsOP2[card]->writeReg(index, value);
#endif
#else//ADLMIDI_HW_OPL
m_chips[chip]->writeReg(address, value);
#endif
}
void OPL3::writeRegI(size_t chip, uint32_t address, uint32_t value)
{
#ifdef ADLMIDI_HW_OPL
writeReg(chip, static_cast<uint16_t>(address), static_cast<uint8_t>(value));
#else//ADLMIDI_HW_OPL
m_chips[chip]->writeReg(static_cast<uint16_t>(address), static_cast<uint8_t>(value));
#endif
}
void OPL3::writePan(size_t chip, uint32_t address, uint32_t value)
{
#ifndef ADLMIDI_HW_OPL
m_chips[chip]->writePan(static_cast<uint16_t>(address), static_cast<uint8_t>(value));
#else
ADL_UNUSED(chip);
ADL_UNUSED(address);
ADL_UNUSED(value);
#endif
}
void OPL3::NoteOff(size_t c)
void OPL3::noteOff(size_t c)
{
size_t card = c / 23, cc = c % 23;
size_t chip = c / 23, cc = c % 23;
if(cc >= 18)
{
regBD[card] &= ~(0x10 >> (cc - 18));
Poke(card, 0xBD, regBD[card]);
m_regBD[chip] &= ~(0x10 >> (cc - 18));
writeRegI(chip, 0xBD, m_regBD[chip]);
return;
}
Poke(card, 0xB0 + Channels[cc], pit[c] & 0xDF);
writeRegI(chip, 0xB0 + g_channelsMap[cc], m_keyBlockFNumCache[c] & 0xDF);
}
void OPL3::NoteOn(unsigned c, double hertz) // Hertz range: 0..131071
void OPL3::noteOn(size_t c1, size_t c2, double hertz) // Hertz range: 0..131071
{
unsigned card = c / 23, cc = c % 23;
unsigned x = 0x2000;
size_t chip = c1 / 23, cc1 = c1 % 23, cc2 = c2 % 23;
uint32_t octave = 0, ftone = 0, mul_offset = 0;
if(hertz < 0 || hertz > 131071) // Avoid infinite loop
if(hertz < 0)
return;
while(hertz >= 1023.5)
//Basic range until max of octaves reaching
while((hertz >= 1023.5) && (octave < 0x1C00))
{
hertz /= 2.0; // Calculate octave
x += 0x400;
octave += 0x400;
}
//Extended range, rely on frequency multiplication increment
while(hertz >= 1022.75)
{
hertz /= 2.0; // Calculate octave
mul_offset++;
}
x += static_cast<unsigned int>(hertz + 0.5);
unsigned chn = Channels[cc];
ftone = octave + static_cast<uint32_t>(hertz + 0.5);
uint32_t chn = g_channelsMap[cc1];
const adldata &patch1 = m_insCache[c1];
const adldata &patch2 = m_insCache[c2 < m_insCache.size() ? c2 : 0];
if(cc >= 18)
if(cc1 < 18)
{
regBD[card] |= (0x10 >> (cc - 18));
Poke(card, 0x0BD, regBD[card]);
x &= ~0x2000u;
//x |= 0x800; // for test
ftone += 0x2000u; /* Key-ON [KON] */
const bool natural_4op = (m_channelCategory[c1] == ChanCat_4op_Master);
const size_t opsCount = natural_4op ? 4 : 2;
const uint16_t op_addr[4] =
{
g_operatorsMap[cc1 * 2 + 0], g_operatorsMap[cc1 * 2 + 1],
g_operatorsMap[cc2 * 2 + 0], g_operatorsMap[cc2 * 2 + 1]
};
const uint32_t ops[4] =
{
patch1.modulator_E862 & 0xFF,
patch1.carrier_E862 & 0xFF,
patch2.modulator_E862 & 0xFF,
patch2.carrier_E862 & 0xFF
};
for(size_t op = 0; op < opsCount; op++)
{
if((op > 0) && (op_addr[op] == 0xFFF))
break;
if(mul_offset > 0)
{
uint32_t dt = ops[op] & 0xF0;
uint32_t mul = ops[op] & 0x0F;
if((mul + mul_offset) > 0x0F)
{
mul_offset = 0;
mul = 0x0F;
}
writeRegI(chip, 0x20 + op_addr[op], (dt | (mul + mul_offset)) & 0xFF);
}
else
{
writeRegI(chip, 0x20 + op_addr[op], ops[op] & 0xFF);
}
}
}
if(chn != 0xFFF)
{
Poke(card, 0xA0 + chn, x & 0xFF);
Poke(card, 0xB0 + chn, pit[c] = static_cast<uint8_t>(x >> 8));
writeRegI(chip , 0xA0 + chn, (ftone & 0xFF));
writeRegI(chip , 0xB0 + chn, (ftone >> 8));
m_keyBlockFNumCache[c1] = (ftone >> 8);
}
if(cc1 >= 18)
{
m_regBD[chip ] |= (0x10 >> (cc1 - 18));
writeRegI(chip , 0x0BD, m_regBD[chip ]);
//x |= 0x800; // for test
}
}
void OPL3::Touch_Real(unsigned c, unsigned volume, uint8_t brightness)
void OPL3::touchNote(size_t c, uint8_t volume, uint8_t brightness)
{
if(volume > 63)
volume = 63;
size_t card = c / 23, cc = c % 23;
const adldata &adli = ins[c];
uint16_t o1 = Operators[cc * 2 + 0];
uint16_t o2 = Operators[cc * 2 + 1];
size_t chip = c / 23, cc = c % 23;
const adldata &adli = m_insCache[c];
uint16_t o1 = g_operatorsMap[cc * 2 + 0];
uint16_t o2 = g_operatorsMap[cc * 2 + 1];
uint8_t x = adli.modulator_40, y = adli.carrier_40;
uint16_t mode = 1; // 2-op AM
uint32_t mode = 1; // 2-op AM
if(four_op_category[c] == 0 || four_op_category[c] == 3)
if(m_channelCategory[c] == ChanCat_Regular ||
m_channelCategory[c] == ChanCat_Rhythm_Bass)
{
mode = adli.feedconn & 1; // 2-op FM or 2-op AM
}
else if(four_op_category[c] == 1 || four_op_category[c] == 2)
else if(m_channelCategory[c] == ChanCat_4op_Master ||
m_channelCategory[c] == ChanCat_4op_Slave)
{
const adldata *i0, *i1;
if(four_op_category[c] == 1)
if(m_channelCategory[c] == ChanCat_4op_Master)
{
i0 = &adli;
i1 = &ins[c + 3];
i1 = &m_insCache[c + 3];
mode = 2; // 4-op xx-xx ops 1&2
}
else
{
i0 = &ins[c - 3];
i0 = &m_insCache[c - 3];
i1 = &adli;
mode = 6; // 4-op xx-xx ops 3&4
}
@ -303,14 +412,14 @@ void OPL3::Touch_Real(unsigned c, unsigned volume, uint8_t brightness)
if(m_musicMode == MODE_RSXX)
{
Poke(card, 0x40 + o1, x);
writeRegI(chip, 0x40 + o1, x);
if(o2 != 0xFFF)
Poke(card, 0x40 + o2, y - volume / 2);
writeRegI(chip, 0x40 + o2, y - volume / 2);
}
else
{
bool do_modulator = do_ops[ mode ][ 0 ] || ScaleModulators;
bool do_carrier = do_ops[ mode ][ 1 ] || ScaleModulators;
bool do_modulator = do_ops[ mode ][ 0 ] || m_scaleModulators;
bool do_carrier = do_ops[ mode ][ 1 ] || m_scaleModulators;
uint32_t modulator = do_modulator ? (x | 63) - volume + volume * (x & 63) / 63 : x;
uint32_t carrier = do_carrier ? (y | 63) - volume + volume * (y & 63) / 63 : y;
@ -324,9 +433,9 @@ void OPL3::Touch_Real(unsigned c, unsigned volume, uint8_t brightness)
carrier = (carrier | 63) - brightness + brightness * (carrier & 63) / 63;
}
Poke(card, 0x40 + o1, modulator);
writeRegI(chip, 0x40 + o1, modulator);
if(o2 != 0xFFF)
Poke(card, 0x40 + o2, carrier);
writeRegI(chip, 0x40 + o2, carrier);
}
// Correct formula (ST3, AdPlug):
@ -351,70 +460,99 @@ void OPL3::Touch(unsigned c, unsigned volume) // Volume maxes at 127*127*127
}
}*/
void OPL3::Patch(uint16_t c, const adldata &adli)
void OPL3::setPatch(size_t c, const adldata &instrument)
{
uint16_t card = c / 23, cc = c % 23;
size_t chip = c / 23, cc = c % 23;
static const uint8_t data[4] = {0x20, 0x60, 0x80, 0xE0};
ins[c] = adli;
uint16_t o1 = Operators[cc * 2 + 0];
uint16_t o2 = Operators[cc * 2 + 1];
unsigned x = adli.modulator_E862, y = adli.carrier_E862;
m_insCache[c] = instrument;
uint16_t o1 = g_operatorsMap[cc * 2 + 0];
uint16_t o2 = g_operatorsMap[cc * 2 + 1];
unsigned x = instrument.modulator_E862, y = instrument.carrier_E862;
for(unsigned a = 0; a < 4; ++a, x >>= 8, y >>= 8)
for(size_t a = 0; a < 4; ++a, x >>= 8, y >>= 8)
{
Poke(card, data[a] + o1, x & 0xFF);
writeRegI(chip, data[a] + o1, x & 0xFF);
if(o2 != 0xFFF)
Poke(card, data[a] + o2, y & 0xFF);
writeRegI(chip, data[a] + o2, y & 0xFF);
}
}
void OPL3::Pan(unsigned c, unsigned value)
void OPL3::setPan(size_t c, uint8_t value)
{
unsigned card = c / 23, cc = c % 23;
if(Channels[cc] != 0xFFF)
Poke(card, 0xC0 + Channels[cc], ins[c].feedconn | value);
}
void OPL3::Silence() // Silence all OPL channels.
{
for(unsigned c = 0; c < NumChannels; ++c)
size_t chip = c / 23, cc = c % 23;
if(g_channelsMap[cc] != 0xFFF)
{
NoteOff(c);
Touch_Real(c, 0);
#ifndef ADLMIDI_HW_OPL
if (m_softPanning)
{
writePan(chip, g_channelsMap[cc], value);
writeRegI(chip, 0xC0 + g_channelsMap[cc], m_insCache[c].feedconn | OPL_PANNING_BOTH);
}
else
{
#endif
int panning = 0;
if(value < 64 + 32) panning |= OPL_PANNING_LEFT;
if(value >= 64 - 32) panning |= OPL_PANNING_RIGHT;
writePan(chip, g_channelsMap[cc], 64);
writeRegI(chip, 0xC0 + g_channelsMap[cc], m_insCache[c].feedconn | panning);
#ifndef ADLMIDI_HW_OPL
}
#endif
}
}
void OPL3::updateFlags()
void OPL3::silenceAll() // Silence all OPL channels.
{
unsigned fours = NumFourOps;
for(unsigned card = 0; card < NumCards; ++card)
for(size_t c = 0; c < m_numChannels; ++c)
{
Poke(card, 0x0BD, regBD[card] = (HighTremoloMode * 0x80
+ HighVibratoMode * 0x40
+ AdlPercussionMode * 0x20));
unsigned fours_this_card = std::min(fours, 6u);
Poke(card, 0x104, (1 << fours_this_card) - 1);
fours -= fours_this_card;
noteOff(c);
touchNote(c, 0);
}
}
void OPL3::updateChannelCategories()
{
const uint32_t fours = m_numFourOps;
for(uint32_t chip = 0, fours_left = fours; chip < m_numChips; ++chip)
{
m_regBD[chip] = (m_deepTremoloMode * 0x80 + m_deepVibratoMode * 0x40 + m_rhythmMode * 0x20);
writeRegI(chip, 0x0BD, m_regBD[chip]);
uint32_t fours_this_chip = std::min(fours_left, static_cast<uint32_t>(6u));
writeRegI(chip, 0x104, (1 << fours_this_chip) - 1);
fours_left -= fours_this_chip;
}
// Mark all channels that are reserved for four-operator function
if(AdlPercussionMode == 1)
for(unsigned a = 0; a < NumCards; ++a)
if(!m_rhythmMode)
{
for(unsigned b = 0; b < 5; ++b)
four_op_category[a * 23 + 18 + b] = static_cast<char>(b + 3);
for(unsigned b = 0; b < 3; ++b)
four_op_category[a * 23 + 6 + b] = 8;
for(size_t a = 0, n = m_numChips; a < n; ++a)
{
for(size_t b = 0; b < 23; ++b)
{
m_channelCategory[a * 23 + b] =
(b >= 18) ? ChanCat_Rhythm_Slave : ChanCat_Regular;
}
}
}
else
{
for(size_t a = 0, n = m_numChips; a < n; ++a)
{
for(size_t b = 0; b < 23; ++b)
{
m_channelCategory[a * 23 + b] =
(b >= 18) ? static_cast<ChanCat>(ChanCat_Rhythm_Bass + (b - 18)) :
(b >= 6 && b < 9) ? ChanCat_Rhythm_Slave : ChanCat_Regular;
}
}
}
unsigned nextfour = 0;
for(unsigned a = 0; a < NumFourOps; ++a)
uint32_t nextfour = 0;
for(uint32_t a = 0; a < fours; ++a)
{
four_op_category[nextfour ] = 1;
four_op_category[nextfour + 3] = 2;
m_channelCategory[nextfour] = ChanCat_4op_Master;
m_channelCategory[nextfour + 3] = ChanCat_4op_Slave;
switch(a % 6)
{
@ -434,19 +572,45 @@ void OPL3::updateFlags()
break;
}
}
/**/
/*
In two-op mode, channels 0..8 go as follows:
Op1[port] Op2[port]
Channel 0: 00 00 03 03
Channel 1: 01 01 04 04
Channel 2: 02 02 05 05
Channel 3: 06 08 09 0B
Channel 4: 07 09 10 0C
Channel 5: 08 0A 11 0D
Channel 6: 12 10 15 13
Channel 7: 13 11 16 14
Channel 8: 14 12 17 15
In four-op mode, channels 0..8 go as follows:
Op1[port] Op2[port] Op3[port] Op4[port]
Channel 0: 00 00 03 03 06 08 09 0B
Channel 1: 01 01 04 04 07 09 10 0C
Channel 2: 02 02 05 05 08 0A 11 0D
Channel 3: CHANNEL 0 SLAVE
Channel 4: CHANNEL 1 SLAVE
Channel 5: CHANNEL 2 SLAVE
Channel 6: 12 10 15 13
Channel 7: 13 11 16 14
Channel 8: 14 12 17 15
Same goes principally for channels 9-17 respectively.
*/
}
void OPL3::updateDeepFlags()
void OPL3::commitDeepFlags()
{
for(unsigned card = 0; card < NumCards; ++card)
for(size_t chip = 0; chip < m_numChips; ++chip)
{
Poke(card, 0x0BD, regBD[card] = (HighTremoloMode * 0x80
+ HighVibratoMode * 0x40
+ AdlPercussionMode * 0x20));
m_regBD[chip] = (m_deepTremoloMode * 0x80 + m_deepVibratoMode * 0x40 + m_rhythmMode * 0x20);
writeRegI(chip, 0x0BD, m_regBD[chip]);
}
}
void OPL3::ChangeVolumeRangesModel(ADLMIDI_VolumeModels volumeModel)
void OPL3::setVolumeScaleModel(ADLMIDI_VolumeModels volumeModel)
{
switch(volumeModel)
{
@ -475,19 +639,37 @@ void OPL3::ChangeVolumeRangesModel(ADLMIDI_VolumeModels volumeModel)
}
}
#ifndef ADLMIDI_HW_OPL
void OPL3::ClearChips()
ADLMIDI_VolumeModels OPL3::getVolumeScaleModel()
{
for(size_t i = 0; i < cardsOP2.size(); i++)
cardsOP2[i].reset(NULL);
cardsOP2.clear();
switch(m_volumeScale)
{
default:
case OPL3::VOLUME_Generic:
return ADLMIDI_VolumeModel_Generic;
case OPL3::VOLUME_NATIVE:
return ADLMIDI_VolumeModel_NativeOPL3;
case OPL3::VOLUME_DMX:
return ADLMIDI_VolumeModel_DMX;
case OPL3::VOLUME_APOGEE:
return ADLMIDI_VolumeModel_APOGEE;
case OPL3::VOLUME_9X:
return ADLMIDI_VolumeModel_9X;
}
}
#ifndef ADLMIDI_HW_OPL
void OPL3::clearChips()
{
for(size_t i = 0; i < m_chips.size(); i++)
m_chips[i].reset(NULL);
m_chips.clear();
}
#endif
void OPL3::Reset(int emulator, unsigned long PCM_RATE, void *audioTickHandler)
void OPL3::reset(int emulator, unsigned long PCM_RATE, void *audioTickHandler)
{
#ifndef ADLMIDI_HW_OPL
ClearChips();
clearChips();
#else
(void)emulator;
(void)PCM_RATE;
@ -495,24 +677,27 @@ void OPL3::Reset(int emulator, unsigned long PCM_RATE, void *audioTickHandler)
#if !defined(ADLMIDI_AUDIO_TICK_HANDLER)
(void)audioTickHandler;
#endif
ins.clear();
pit.clear();
regBD.clear();
m_insCache.clear();
m_keyBlockFNumCache.clear();
m_regBD.clear();
#ifndef ADLMIDI_HW_OPL
cardsOP2.resize(NumCards, AdlMIDI_SPtr<OPLChipBase>());
m_chips.resize(m_numChips, AdlMIDI_SPtr<OPLChipBase>());
#endif
NumChannels = NumCards * 23;
ins.resize(NumChannels, adl[adlDefaultNumber]);
pit.resize(NumChannels, 0);
regBD.resize(NumCards, 0);
four_op_category.resize(NumChannels, 0);
const struct adldata defaultInsCache = { 0x1557403,0x005B381, 0x49,0x80, 0x4, +0 };
m_numChannels = m_numChips * 23;
m_insCache.resize(m_numChannels, defaultInsCache);
m_keyBlockFNumCache.resize(m_numChannels, 0);
m_regBD.resize(m_numChips, 0);
m_channelCategory.resize(m_numChannels, 0);
for(unsigned p = 0, a = 0; a < NumCards; ++a)
for(size_t p = 0, a = 0; a < m_numChips; ++a)
{
for(unsigned b = 0; b < 18; ++b) four_op_category[p++] = 0;
for(unsigned b = 0; b < 5; ++b) four_op_category[p++] = 8;
for(size_t b = 0; b < 18; ++b)
m_channelCategory[p++] = 0;
for(size_t b = 0; b < 5; ++b)
m_channelCategory[p++] = ChanCat_Rhythm_Slave;
}
static const uint16_t data[] =
@ -521,15 +706,17 @@ void OPL3::Reset(int emulator, unsigned long PCM_RATE, void *audioTickHandler)
0x105, 0, 0x105, 1, 0x105, 0, // Pulse OPL3 enable
0x001, 32, 0x105, 1 // Enable wave, OPL3 extensions
};
unsigned fours = NumFourOps;
// size_t fours = m_numFourOps;
for(size_t i = 0; i < NumCards; ++i)
for(size_t i = 0; i < m_numChips; ++i)
{
#ifndef ADLMIDI_HW_OPL
OPLChipBase *chip;
switch(emulator)
{
default:
assert(false);
abort();
#ifndef ADLMIDI_DISABLE_NUKED_EMULATOR
case ADLMIDI_EMU_NUKED: /* Latest Nuked OPL3 */
chip = new NukedOPL3;
@ -544,91 +731,23 @@ void OPL3::Reset(int emulator, unsigned long PCM_RATE, void *audioTickHandler)
break;
#endif
}
cardsOP2[i].reset(chip);
m_chips[i].reset(chip);
chip->setChipId((uint32_t)i);
chip->setRate((uint32_t)PCM_RATE);
if(runAtPcmRate)
if(m_runAtPcmRate)
chip->setRunningAtPcmRate(true);
# if defined(ADLMIDI_AUDIO_TICK_HANDLER)
chip->setAudioTickHandlerInstance(audioTickHandler);
# endif
#endif // ADLMIDI_HW_OPL
for(unsigned a = 0; a < 18; ++a) Poke(i, 0xB0 + Channels[a], 0x00);
for(unsigned a = 0; a < sizeof(data) / sizeof(*data); a += 2)
Poke(i, data[a], static_cast<uint8_t>(data[a + 1]));
Poke(i, 0x0BD, regBD[i] = (HighTremoloMode * 0x80
+ HighVibratoMode * 0x40
+ AdlPercussionMode * 0x20));
unsigned fours_this_card = std::min(fours, 6u);
Poke(i, 0x104, (1 << fours_this_card) - 1);
//fprintf(stderr, "Card %u: %u four-ops.\n", card, fours_this_card);
fours -= fours_this_card;
/* Clean-up channels from any playing junk sounds */
for(size_t a = 0; a < 18; ++a)
writeRegI(i, 0xB0 + g_channelsMap[a], 0x00);
for(size_t a = 0; a < sizeof(data) / sizeof(*data); a += 2)
writeRegI(i, data[a], (data[a + 1]));
}
// Mark all channels that are reserved for four-operator function
if(AdlPercussionMode == 1)
{
for(unsigned a = 0; a < NumCards; ++a)
{
for(unsigned b = 0; b < 5; ++b) four_op_category[a * 23 + 18 + b] = static_cast<char>(b + 3);
for(unsigned b = 0; b < 3; ++b) four_op_category[a * 23 + 6 + b] = 8;
}
}
unsigned nextfour = 0;
for(unsigned a = 0; a < NumFourOps; ++a)
{
four_op_category[nextfour ] = 1;
four_op_category[nextfour + 3] = 2;
switch(a % 6)
{
case 0:
case 1:
nextfour += 1;
break;
case 2:
nextfour += 9 - 2;
break;
case 3:
case 4:
nextfour += 1;
break;
case 5:
nextfour += 23 - 9 - 2;
break;
}
}
/**/
/*
In two-op mode, channels 0..8 go as follows:
Op1[port] Op2[port]
Channel 0: 00 00 03 03
Channel 1: 01 01 04 04
Channel 2: 02 02 05 05
Channel 3: 06 08 09 0B
Channel 4: 07 09 10 0C
Channel 5: 08 0A 11 0D
Channel 6: 12 10 15 13
Channel 7: 13 11 16 14
Channel 8: 14 12 17 15
In four-op mode, channels 0..8 go as follows:
Op1[port] Op2[port] Op3[port] Op4[port]
Channel 0: 00 00 03 03 06 08 09 0B
Channel 1: 01 01 04 04 07 09 10 0C
Channel 2: 02 02 05 05 08 0A 11 0D
Channel 3: CHANNEL 0 SLAVE
Channel 4: CHANNEL 1 SLAVE
Channel 5: CHANNEL 2 SLAVE
Channel 6: 12 10 15 13
Channel 7: 13 11 16 14
Channel 8: 14 12 17 15
Same goes principally for channels 9-17 respectively.
*/
Silence();
updateChannelCategories();
silenceAll();
}

View file

@ -27,53 +27,58 @@ std::string ADLMIDI_ErrorString;
// Generator callback on audio rate ticks
#if defined(ADLMIDI_AUDIO_TICK_HANDLER)
void adl_audioTickHandler(void *instance, uint32_t chipId, uint32_t rate)
{
reinterpret_cast<MIDIplay *>(instance)->AudioTick(chipId, rate);
}
#endif
int adlRefreshNumCards(ADL_MIDIPlayer *device)
int adlCalculateFourOpChannels(MIDIplay *play, bool silent)
{
unsigned n_fourop[2] = {0, 0}, n_total[2] = {0, 0};
MIDIplay *play = reinterpret_cast<MIDIplay *>(device->adl_midiPlayer);
size_t n_fourop[2] = {0, 0}, n_total[2] = {0, 0};
//Automatically calculate how much 4-operator channels is necessary
if(play->opl.AdlBank == ~0u)
#ifndef DISABLE_EMBEDDED_BANKS
if(play->m_synth.m_embeddedBank == OPL3::CustomBankTag)
#endif
{
//For custom bank
OPL3::BankMap::iterator it = play->opl.dynamic_banks.begin();
OPL3::BankMap::iterator end = play->opl.dynamic_banks.end();
OPL3::BankMap::iterator it = play->m_synth.m_insBanks.begin();
OPL3::BankMap::iterator end = play->m_synth.m_insBanks.end();
for(; it != end; ++it)
{
uint16_t bank = it->first;
unsigned div = (bank & OPL3::PercussionTag) ? 1 : 0;
for(unsigned i = 0; i < 128; ++i)
size_t bank = it->first;
size_t div = (bank & OPL3::PercussionTag) ? 1 : 0;
for(size_t i = 0; i < 128; ++i)
{
adlinsdata2 &ins = it->second.ins[i];
if(ins.flags & adlinsdata::Flag_NoSound)
continue;
if((ins.adl[0] != ins.adl[1]) && ((ins.flags & adlinsdata::Flag_Pseudo4op) == 0))
if((ins.flags & adlinsdata::Flag_Real4op) != 0)
++n_fourop[div];
++n_total[div];
}
}
}
#ifndef DISABLE_EMBEDDED_BANKS
else
{
//For embedded bank
for(unsigned a = 0; a < 256; ++a)
for(size_t a = 0; a < 256; ++a)
{
unsigned insno = banks[play->m_setup.AdlBank][a];
size_t insno = banks[play->m_setup.bankId][a];
if(insno == 198)
continue;
++n_total[a / 128];
adlinsdata2 ins(adlins[insno]);
if(ins.flags & adlinsdata::Flag_Real4op)
adlinsdata2 ins = adlinsdata2::from_adldata(::adlins[insno]);
if((ins.flags & adlinsdata::Flag_Real4op) != 0)
++n_fourop[a / 128];
}
}
#endif
unsigned numFourOps = 0;
size_t numFourOps = 0;
// All 2ops (no 4ops)
if((n_fourop[0] == 0) && (n_fourop[1] == 0))
@ -94,7 +99,10 @@ int adlRefreshNumCards(ADL_MIDIPlayer *device)
: (play->m_setup.NumCards == 1 ? 1 : play->m_setup.NumCards * 4);
*/
play->opl.NumFourOps = play->m_setup.NumFourOps = (numFourOps * play->m_setup.NumCards);
play->m_synth.m_numFourOps = static_cast<unsigned>(numFourOps * play->m_synth.m_numChips);
// Update channel categories and set up four-operator channels
if(!silent)
play->m_synth.updateChannelCategories();
return 0;
}

File diff suppressed because it is too large Load diff

View file

@ -37,6 +37,8 @@
#include <math.h>
#include <stdlib.h>
#include <string.h>
#include <vector>
#include <memory>
#include "dbopl.h"
#if defined(__GNUC__) && __GNUC__ > 3
@ -70,6 +72,37 @@
#define PI 3.14159265358979323846
#endif
struct NoCopy {
NoCopy() {}
private:
NoCopy(const NoCopy &);
NoCopy &operator=(const NoCopy &);
};
#if !defined(_WIN32)
#include <pthread.h>
struct Mutex : NoCopy {
Mutex() { pthread_mutex_init(&m, NULL);}
~Mutex() { pthread_mutex_destroy(&m); }
void lock() { pthread_mutex_lock(&m); }
void unlock() { pthread_mutex_unlock(&m); }
pthread_mutex_t m;
};
#else
#include <windows.h>
struct Mutex : NoCopy {
Mutex() { InitializeCriticalSection(&m); }
~Mutex() { DeleteCriticalSection(&m); }
void lock() { EnterCriticalSection(&m); }
void unlock() { LeaveCriticalSection(&m); }
CRITICAL_SECTION m;
};
#endif
struct MutexHolder : NoCopy {
explicit MutexHolder(Mutex &m) : m(m) { m.lock(); }
~MutexHolder() { m.unlock(); }
Mutex &m;
};
namespace DBOPL {
#define OPLRATE ((double)(14318180.0 / 288.0))
@ -219,6 +252,29 @@ static const Bit8u KslShiftTable[4] = {
31,1,2,0
};
// Pan law table
static const Bit16u PanLawTable[] =
{
65535, 65529, 65514, 65489, 65454, 65409, 65354, 65289,
65214, 65129, 65034, 64929, 64814, 64689, 64554, 64410,
64255, 64091, 63917, 63733, 63540, 63336, 63123, 62901,
62668, 62426, 62175, 61914, 61644, 61364, 61075, 60776,
60468, 60151, 59825, 59489, 59145, 58791, 58428, 58057,
57676, 57287, 56889, 56482, 56067, 55643, 55211, 54770,
54320, 53863, 53397, 52923, 52441, 51951, 51453, 50947,
50433, 49912, 49383, 48846, 48302, 47750, 47191,
46340, /* Center left */
46340, /* Center right */
45472, 44885, 44291, 43690, 43083, 42469, 41848, 41221,
40588, 39948, 39303, 38651, 37994, 37330, 36661, 35986,
35306, 34621, 33930, 33234, 32533, 31827, 31116, 30400,
29680, 28955, 28225, 27492, 26754, 26012, 25266, 24516,
23762, 23005, 22244, 21480, 20713, 19942, 19169, 18392,
17613, 16831, 16046, 15259, 14469, 13678, 12884, 12088,
11291, 10492, 9691, 8888, 8085, 7280, 6473, 5666,
4858, 4050, 3240, 2431, 1620, 810, 0
};
//Generate a table index and table shift value using input value from a selected rate
static void EnvelopeSelect( Bit8u val, Bit8u& index, Bit8u& shift ) {
if ( val < 13 * 4 ) { //Rate 0 - 12
@ -442,6 +498,7 @@ Bits Operator::TemplateVolume( ) {
return vol;
}
//In sustain phase, but not sustaining, do regular release
/* fall through */
case RELEASE:
vol += RateForward( releaseAdd );;
if ( GCC_UNLIKELY(vol >= ENV_MAX) ) {
@ -757,6 +814,11 @@ void Channel::WriteC0(const Chip* chip, Bit8u val) {
UpdateSynth(chip);
}
void Channel::WritePan(Bit8u val) {
panLeft = PanLawTable[val & 0x7F];
panRight = PanLawTable[0x7F - (val & 0x7F)];
}
void Channel::UpdateSynth( const Chip* chip ) {
//Select the new synth mode
if ( chip->opl3Active ) {
@ -971,8 +1033,8 @@ Channel* Channel::BlockTemplate( Chip* chip, Bit32u samples, Bit32s* output ) {
case sm3AMFM:
case sm3FMAM:
case sm3AMAM:
output[ i * 2 + 0 ] += sample & maskLeft;
output[ i * 2 + 1 ] += sample & maskRight;
output[ i * 2 + 0 ] += (sample * panLeft / 65535) & maskLeft;
output[ i * 2 + 1 ] += (sample * panRight / 65535) & maskRight;
break;
default:
break;
@ -1265,21 +1327,56 @@ void Chip::GenerateBlock3_Mix( Bitu total, Bit32s* output ) {
}
}
void Chip::Setup( Bit32u rate ) {
struct CacheEntry {
Bit32u rate;
Bit32u freqMul[16];
Bit32u linearRates[76];
Bit32u attackRates[76];
};
struct Cache : NoCopy {
~Cache();
Mutex mutex;
std::vector<CacheEntry *> entries;
};
static Cache cache;
Cache::~Cache()
{
for ( size_t i = 0, n = entries.size(); i < n; ++i )
delete entries[i];
}
static const CacheEntry *CacheLookupRateDependent( Bit32u rate )
{
for ( size_t i = 0, n = cache.entries.size(); i < n; ++i ) {
const CacheEntry *entry = cache.entries[i];
if (entry->rate == rate)
return entry;
}
return NULL;
}
static const CacheEntry &ComputeRateDependent( Bit32u rate )
{
{
MutexHolder lock( cache.mutex );
if (const CacheEntry *entry = CacheLookupRateDependent( rate ))
return *entry;
}
double original = OPLRATE;
// double original = rate;
double scale = original / (double)rate;
//Noise counter is run at the same precision as general waves
noiseAdd = (Bit32u)( 0.5 + scale * ( 1 << LFO_SH ) );
noiseCounter = 0;
noiseValue = 1; //Make sure it triggers the noise xor the first time
//The low frequency oscillation counter
//Every time his overflows vibrato and tremoloindex are increased
lfoAdd = (Bit32u)( 0.5 + scale * ( 1 << LFO_SH ) );
lfoCounter = 0;
vibratoIndex = 0;
tremoloIndex = 0;
#if __cplusplus >= 201103L
std::unique_ptr<CacheEntry> entry(new CacheEntry);
#else
std::auto_ptr<CacheEntry> entry(new CacheEntry);
#endif
entry->rate = rate;
Bit32u *freqMul = entry->freqMul;
Bit32u *linearRates = entry->linearRates;
Bit32u *attackRates = entry->attackRates;
//With higher octave this gets shifted up
//-1 since the freqCreateTable = *2
@ -1301,6 +1398,7 @@ void Chip::Setup( Bit32u rate ) {
EnvelopeSelect( i, index, shift );
linearRates[i] = (Bit32u)( scale * (EnvelopeIncreaseTable[ index ] << ( RATE_SH + ENV_EXTRA - shift - 3 )));
}
// Bit32s attackDiffs[62];
//Generate the best matching attack rate
for ( Bit8u i = 0; i < 62; i++ ) {
@ -1353,6 +1451,36 @@ void Chip::Setup( Bit32u rate ) {
//This should provide instant volume maximizing
attackRates[i] = 8 << RATE_SH;
}
MutexHolder lock( cache.mutex );
if (const CacheEntry *entry = CacheLookupRateDependent( rate ))
return *entry;
cache.entries.push_back(entry.get());
return *entry.release();
}
void Chip::Setup( Bit32u rate ) {
double original = OPLRATE;
// double original = rate;
double scale = original / (double)rate;
//Noise counter is run at the same precision as general waves
noiseAdd = (Bit32u)( 0.5 + scale * ( 1 << LFO_SH ) );
noiseCounter = 0;
noiseValue = 1; //Make sure it triggers the noise xor the first time
//The low frequency oscillation counter
//Every time his overflows vibrato and tremoloindex are increased
lfoAdd = (Bit32u)( 0.5 + scale * ( 1 << LFO_SH ) );
lfoCounter = 0;
vibratoIndex = 0;
tremoloIndex = 0;
const CacheEntry &entry = ComputeRateDependent( rate );
freqMul = entry.freqMul;
linearRates = entry.linearRates;
attackRates = entry.attackRates;
//Setup the channels with the correct four op flags
//Channels are accessed through a table so they appear linear here
chan[ 0].fourMask = 0x00 | ( 1 << 0 );
@ -1388,6 +1516,10 @@ void Chip::Setup( Bit32u rate ) {
WriteReg( i, 0xff );
WriteReg( i, 0x0 );
}
for ( int i = 0; i < 18; i++ ) {
chan[i].WritePan( 0x40 );
}
}
static bool doneTables = false;
@ -1614,5 +1746,14 @@ void Handler::Init( Bitu rate ) {
chip.Setup( static_cast<Bit32u>(rate) );
}
void Handler::WritePan( Bit32u reg, Bit8u val )
{
Bitu index;
index = ((reg >> 4) & 0x10) | (reg & 0xf);
if (ChanOffsetTable[index]) {
Channel* regChan = (Channel*)(((char *)&chip) + ChanOffsetTable[index]);
regChan->WritePan(val);
}
}
} //Namespace DBOPL

View file

@ -75,13 +75,13 @@ typedef enum {
sm3AMAM,
sm6Start,
sm2Percussion,
sm3Percussion,
sm3Percussion
} SynthMode;
//Shifts for the values contained in chandata variable
enum {
SHIFT_KSLBASE = 16,
SHIFT_KEYCODE = 24,
SHIFT_KEYCODE = 24
};
struct Operator {
@ -91,7 +91,7 @@ public:
MASK_KSR = 0x10,
MASK_SUSTAIN = 0x20,
MASK_VIBRATO = 0x40,
MASK_TREMOLO = 0x80,
MASK_TREMOLO = 0x80
};
typedef enum {
@ -99,7 +99,7 @@ public:
RELEASE,
SUSTAIN,
DECAY,
ATTACK,
ATTACK
} State;
VolumeHandler volHandler;
@ -192,6 +192,9 @@ struct Channel {
Bit8s maskLeft; //Sign extended values for both channel's panning
Bit8s maskRight;
Bit16u panLeft; // Extended behavior, scale values for soft panning
Bit16u panRight;
//Forward the channel data to the operators of the channel
void SetChanData( const Chip* chip, Bit32u data );
//Change in the chandata, check for new values and if we have to forward to operators
@ -201,6 +204,8 @@ struct Channel {
void WriteB0( const Chip* chip, Bit8u val );
void WriteC0( const Chip* chip, Bit8u val );
void WritePan( Bit8u val );
//call this for the first channel
template< bool opl3Mode >
void GeneratePercussion( Chip* chip, Bit32s* output );
@ -222,11 +227,11 @@ struct Chip {
Bit32u noiseValue;
//Frequency scales for the different multiplications
Bit32u freqMul[16];
const Bit32u *freqMul/*[16]*/;
//Rates for decay and release for rate of this chip
Bit32u linearRates[76];
const Bit32u *linearRates/*[76]*/;
//Best match attack rates for the rate of this chip
Bit32u attackRates[76];
const Bit32u *attackRates/*[76]*/;
//18 channels with 2 operators each
Channel chan[18];
@ -271,6 +276,7 @@ struct Chip {
struct Handler {
DBOPL::Chip chip;
void WritePan( Bit32u port, Bit8u val );
Bit32u WriteAddr( Bit32u port, Bit8u val );
void WriteReg( Bit32u addr, Bit8u val );
void GenerateArr(Bit32s *out, Bitu *samples);

View file

@ -1,3 +1,23 @@
/*
* Interfaces over Yamaha OPL3 (YMF262) chip emulators
*
* Copyright (C) 2017-2018 Vitaly Novichkov (Wohlstand)
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
#include "dosbox_opl3.h"
#include "dosbox/dbopl.h"
#include <new>
@ -41,6 +61,12 @@ void DosBoxOPL3::writeReg(uint16_t addr, uint8_t data)
chip_r->WriteReg(static_cast<Bit32u>(addr), data);
}
void DosBoxOPL3::writePan(uint16_t addr, uint8_t data)
{
DBOPL::Handler *chip_r = reinterpret_cast<DBOPL::Handler*>(m_chip);
chip_r->WritePan(static_cast<Bit32u>(addr), data);
}
void DosBoxOPL3::nativeGenerateN(int16_t *output, size_t frames)
{
DBOPL::Handler *chip_r = reinterpret_cast<DBOPL::Handler*>(m_chip);
@ -50,5 +76,5 @@ void DosBoxOPL3::nativeGenerateN(int16_t *output, size_t frames)
const char *DosBoxOPL3::emulatorName()
{
return "DosBox 0.74-r4111 OPL3";
return "DOSBox 0.74-r4111 OPL3";
}

View file

@ -1,3 +1,23 @@
/*
* Interfaces over Yamaha OPL3 (YMF262) chip emulators
*
* Copyright (C) 2017-2018 Vitaly Novichkov (Wohlstand)
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
#ifndef DOSBOX_OPL3_H
#define DOSBOX_OPL3_H
@ -14,6 +34,7 @@ public:
void setRate(uint32_t rate) override;
void reset() override;
void writeReg(uint16_t addr, uint8_t data) override;
void writePan(uint16_t addr, uint8_t data) override;
void nativePreGenerate() override {}
void nativePostGenerate() override {}
void nativeGenerateN(int16_t *output, size_t frames) override;

View file

@ -1,16 +1,19 @@
/*
* Copyright (C) 2013-2018 Alexey Khokholov (Nuke.YKT)
*
* 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 2
* of the License, or (at your option) any later version.
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* 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 General Public License for more details.
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*
* Nuked OPL3 emulator.
* Thanks:
@ -174,6 +177,32 @@ static const Bit8u ch_slot[18] = {
0, 1, 2, 6, 7, 8, 12, 13, 14, 18, 19, 20, 24, 25, 26, 30, 31, 32
};
/*
* Pan law table
*/
static const Bit16u panlawtable[] =
{
65535, 65529, 65514, 65489, 65454, 65409, 65354, 65289,
65214, 65129, 65034, 64929, 64814, 64689, 64554, 64410,
64255, 64091, 63917, 63733, 63540, 63336, 63123, 62901,
62668, 62426, 62175, 61914, 61644, 61364, 61075, 60776,
60468, 60151, 59825, 59489, 59145, 58791, 58428, 58057,
57676, 57287, 56889, 56482, 56067, 55643, 55211, 54770,
54320, 53863, 53397, 52923, 52441, 51951, 51453, 50947,
50433, 49912, 49383, 48846, 48302, 47750, 47191,
46340, /* Center left */
46340, /* Center right */
45472, 44885, 44291, 43690, 43083, 42469, 41848, 41221,
40588, 39948, 39303, 38651, 37994, 37330, 36661, 35986,
35306, 34621, 33930, 33234, 32533, 31827, 31116, 30400,
29680, 28955, 28225, 27492, 26754, 26012, 25266, 24516,
23762, 23005, 22244, 21480, 20713, 19942, 19169, 18392,
17613, 16831, 16046, 15259, 14469, 13678, 12884, 12088,
11291, 10492, 9691, 8888, 8085, 7280, 6473, 5666,
4858, 4050, 3240, 2431, 1620, 810, 0
};
/*
* Envelope generator
*/
@ -1068,7 +1097,7 @@ void OPL3_Generate(opl3_chip *chip, Bit16s *buf)
{
accm += *chip->channel[ii].out[jj];
}
chip->mixbuff[0] += (Bit16s)(accm & chip->channel[ii].cha);
chip->mixbuff[0] += (Bit16s)((accm * chip->channel[ii].chl / 65535) & chip->channel[ii].cha);
}
for (ii = 15; ii < 18; ii++)
@ -1097,7 +1126,7 @@ void OPL3_Generate(opl3_chip *chip, Bit16s *buf)
{
accm += *chip->channel[ii].out[jj];
}
chip->mixbuff[1] += (Bit16s)(accm & chip->channel[ii].chb);
chip->mixbuff[1] += (Bit16s)((accm * chip->channel[ii].chr / 65535) & chip->channel[ii].chb);
}
for (ii = 33; ii < 36; ii++)
@ -1229,6 +1258,8 @@ void OPL3_Reset(opl3_chip *chip, Bit32u samplerate)
chip->channel[channum].chtype = ch_2op;
chip->channel[channum].cha = 0xffff;
chip->channel[channum].chb = 0xffff;
chip->channel[channum].chl = 46340;
chip->channel[channum].chr = 46340;
chip->channel[channum].ch_num = channum;
OPL3_ChannelSetupAlg(&chip->channel[channum]);
}
@ -1238,6 +1269,19 @@ void OPL3_Reset(opl3_chip *chip, Bit32u samplerate)
chip->vibshift = 1;
}
static void OPL3_ChannelWritePan(opl3_channel *channel, Bit8u data)
{
channel->chl = panlawtable[data & 0x7F];
channel->chr = panlawtable[0x7F - (data & 0x7F)];
}
void OPL3_WritePan(opl3_chip *chip, Bit16u reg, Bit8u v)
{
Bit8u high = (reg >> 8) & 0x01;
Bit8u regm = reg & 0xff;
OPL3_ChannelWritePan(&chip->channel[9 * high + (regm & 0x0f)], v);
}
void OPL3_WriteReg(opl3_chip *chip, Bit16u reg, Bit8u v)
{
Bit8u high = (reg >> 8) & 0x01;

View file

@ -1,16 +1,19 @@
/*
* Copyright (C) 2013-2018 Alexey Khokholov (Nuke.YKT)
*
* 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 2
* of the License, or (at your option) any later version.
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* 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 General Public License for more details.
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*
* Nuked OPL3 emulator.
* Thanks:
@ -98,6 +101,7 @@ struct _opl3_channel {
Bit8u alg;
Bit8u ksv;
Bit16u cha, chb;
Bit16u chl, chr;
Bit8u ch_num;
};
@ -150,6 +154,7 @@ void OPL3_GenerateResampled(opl3_chip *chip, Bit16s *buf);
void OPL3_Reset(opl3_chip *chip, Bit32u samplerate);
void OPL3_WriteReg(opl3_chip *chip, Bit16u reg, Bit8u v);
void OPL3_WriteRegBuffered(opl3_chip *chip, Bit16u reg, Bit8u v);
void OPL3_WritePan(opl3_chip *chip, Bit16u reg, Bit8u v);
void OPL3_GenerateStream(opl3_chip *chip, Bit16s *sndptr, Bit32u numsamples);
void OPL3_GenerateStreamMix(opl3_chip *chip, Bit16s *sndptr, Bit32u numsamples);

View file

@ -229,6 +229,32 @@ static const Bit8u ch_slot[18] = {
0, 1, 2, 6, 7, 8, 12, 13, 14, 18, 19, 20, 24, 25, 26, 30, 31, 32
};
/*
* Pan law table
*/
static const Bit16u panlawtable[] =
{
65535, 65529, 65514, 65489, 65454, 65409, 65354, 65289,
65214, 65129, 65034, 64929, 64814, 64689, 64554, 64410,
64255, 64091, 63917, 63733, 63540, 63336, 63123, 62901,
62668, 62426, 62175, 61914, 61644, 61364, 61075, 60776,
60468, 60151, 59825, 59489, 59145, 58791, 58428, 58057,
57676, 57287, 56889, 56482, 56067, 55643, 55211, 54770,
54320, 53863, 53397, 52923, 52441, 51951, 51453, 50947,
50433, 49912, 49383, 48846, 48302, 47750, 47191,
46340, /* Center left */
46340, /* Center right */
45472, 44885, 44291, 43690, 43083, 42469, 41848, 41221,
40588, 39948, 39303, 38651, 37994, 37330, 36661, 35986,
35306, 34621, 33930, 33234, 32533, 31827, 31116, 30400,
29680, 28955, 28225, 27492, 26754, 26012, 25266, 24516,
23762, 23005, 22244, 21480, 20713, 19942, 19169, 18392,
17613, 16831, 16046, 15259, 14469, 13678, 12884, 12088,
11291, 10492, 9691, 8888, 8085, 7280, 6473, 5666,
4858, 4050, 3240, 2431, 1620, 810, 0
};
/*
* Envelope generator
*/
@ -1082,7 +1108,7 @@ void OPL3v17_Generate(opl3_chip *chip, Bit16s *buf)
{
accm += *chip->channel[ii].out[jj];
}
chip->mixbuff[0] += (Bit16s)(accm & chip->channel[ii].cha);
chip->mixbuff[0] += (Bit16s)((accm * chip->channel[ii].chl / 65535) & chip->channel[ii].cha);
}
for (ii = 15; ii < 18; ii++)
@ -1121,7 +1147,7 @@ void OPL3v17_Generate(opl3_chip *chip, Bit16s *buf)
{
accm += *chip->channel[ii].out[jj];
}
chip->mixbuff[1] += (Bit16s)(accm & chip->channel[ii].chb);
chip->mixbuff[1] += (Bit16s)((accm * chip->channel[ii].chr / 65535) & chip->channel[ii].chb);
}
for (ii = 33; ii < 36; ii++)
@ -1220,8 +1246,10 @@ void OPL3v17_Reset(opl3_chip *chip, Bit32u samplerate)
chip->channel[channum].out[2] = &chip->zeromod;
chip->channel[channum].out[3] = &chip->zeromod;
chip->channel[channum].chtype = ch_2op;
chip->channel[channum].cha = ~0;
chip->channel[channum].chb = ~0;
chip->channel[channum].cha = 0xffff;
chip->channel[channum].chb = 0xffff;
chip->channel[channum].chl = 46340;
chip->channel[channum].chr = 46340;
OPL3_ChannelSetupAlg(&chip->channel[channum]);
}
chip->noise = 0x306600;
@ -1230,6 +1258,19 @@ void OPL3v17_Reset(opl3_chip *chip, Bit32u samplerate)
chip->vibshift = 1;
}
static void OPL3v17_ChannelWritePan(opl3_channel *channel, Bit8u data)
{
channel->chl = panlawtable[data & 0x7F];
channel->chr = panlawtable[0x7F - (data & 0x7F)];
}
void OPL3v17_WritePan(opl3_chip *chip, Bit16u reg, Bit8u v)
{
Bit8u high = (reg >> 8) & 0x01;
Bit8u regm = reg & 0xff;
OPL3v17_ChannelWritePan(&chip->channel[9 * high + (regm & 0x0f)], v);
}
void OPL3v17_WriteReg(opl3_chip *chip, Bit16u reg, Bit8u v)
{
Bit8u high = (reg >> 8) & 0x01;

View file

@ -103,6 +103,7 @@ struct _opl3_channel {
Bit8u alg;
Bit8u ksv;
Bit16u cha, chb;
Bit16u chl, chr;
};
typedef struct _opl3_writebuf {
@ -142,6 +143,7 @@ struct _opl3_chip {
void OPL3v17_Generate(opl3_chip *chip, Bit16s *buf);
void OPL3v17_GenerateResampled(opl3_chip *chip, Bit16s *buf);
void OPL3v17_Reset(opl3_chip *chip, Bit32u samplerate);
void OPL3v17_WritePan(opl3_chip *chip, Bit16u reg, Bit8u v);
void OPL3v17_WriteReg(opl3_chip *chip, Bit16u reg, Bit8u v);
void OPL3v17_WriteRegBuffered(opl3_chip *chip, Bit16u reg, Bit8u v);
void OPL3v17_GenerateStream(opl3_chip *chip, Bit16s *sndptr, Bit32u numsamples);

View file

@ -1,3 +1,23 @@
/*
* Interfaces over Yamaha OPL3 (YMF262) chip emulators
*
* Copyright (C) 2017-2018 Vitaly Novichkov (Wohlstand)
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
#include "nuked_opl3.h"
#include "nuked/nukedopl3.h"
#include <cstring>
@ -37,6 +57,12 @@ void NukedOPL3::writeReg(uint16_t addr, uint8_t data)
OPL3_WriteRegBuffered(chip_r, addr, data);
}
void NukedOPL3::writePan(uint16_t addr, uint8_t data)
{
opl3_chip *chip_r = reinterpret_cast<opl3_chip*>(m_chip);
OPL3_WritePan(chip_r, addr, data);
}
void NukedOPL3::nativeGenerate(int16_t *frame)
{
opl3_chip *chip_r = reinterpret_cast<opl3_chip*>(m_chip);

View file

@ -1,3 +1,23 @@
/*
* Interfaces over Yamaha OPL3 (YMF262) chip emulators
*
* Copyright (C) 2017-2018 Vitaly Novichkov (Wohlstand)
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
#ifndef NUKED_OPL3_H
#define NUKED_OPL3_H
@ -14,6 +34,7 @@ public:
void setRate(uint32_t rate) override;
void reset() override;
void writeReg(uint16_t addr, uint8_t data) override;
void writePan(uint16_t addr, uint8_t data) override;
void nativePreGenerate() override {}
void nativePostGenerate() override {}
void nativeGenerate(int16_t *frame) override;

View file

@ -1,3 +1,23 @@
/*
* Interfaces over Yamaha OPL3 (YMF262) chip emulators
*
* Copyright (C) 2017-2018 Vitaly Novichkov (Wohlstand)
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
#include "nuked_opl3_v174.h"
#include "nuked/nukedopl3_174.h"
#include <cstring>
@ -37,6 +57,12 @@ void NukedOPL3v174::writeReg(uint16_t addr, uint8_t data)
OPL3v17_WriteReg(chip_r, addr, data);
}
void NukedOPL3v174::writePan(uint16_t addr, uint8_t data)
{
opl3_chip *chip_r = reinterpret_cast<opl3_chip*>(m_chip);
OPL3v17_WritePan(chip_r, addr, data);
}
void NukedOPL3v174::nativeGenerate(int16_t *frame)
{
opl3_chip *chip_r = reinterpret_cast<opl3_chip*>(m_chip);

View file

@ -1,3 +1,23 @@
/*
* Interfaces over Yamaha OPL3 (YMF262) chip emulators
*
* Copyright (C) 2017-2018 Vitaly Novichkov (Wohlstand)
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
#ifndef NUKED_OPL3174_H
#define NUKED_OPL3174_H
@ -14,6 +34,7 @@ public:
void setRate(uint32_t rate) override;
void reset() override;
void writeReg(uint16_t addr, uint8_t data) override;
void writePan(uint16_t addr, uint8_t data) override;
void nativePreGenerate() override {}
void nativePostGenerate() override {}
void nativeGenerate(int16_t *frame) override;

View file

@ -1,3 +1,21 @@
/*
* Copyright (C) 2017-2018 Vitaly Novichkov (Wohlstand)
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
#ifndef ONP_CHIP_BASE_H
#define ONP_CHIP_BASE_H
@ -43,6 +61,9 @@ public:
virtual void reset() = 0;
virtual void writeReg(uint16_t addr, uint8_t data) = 0;
// extended
virtual void writePan(uint16_t addr, uint8_t data) { (void)addr; (void)data; }
virtual void nativePreGenerate() = 0;
virtual void nativePostGenerate() = 0;
virtual void nativeGenerate(int16_t *frame) = 0;

View file

@ -236,8 +236,8 @@ void OPLChipBaseT<T>::resampledGenerate(int32_t *output)
rsm->out_count = 1;
rsm->out_data = f_out;
}
output[0] = std::lround(f_out[0]);
output[1] = std::lround(f_out[1]);
output[0] = static_cast<int32_t>(std::lround(f_out[0]));
output[1] = static_cast<int32_t>(std::lround(f_out[1]));
}
#else
template <class T>

View file

@ -0,0 +1,300 @@
/*
* FileAndMemoryReader - a tiny helper to utify file reading from a disk and memory block
*
* Copyright (c) 2015-2018 Vitaly Novichkov <admin@wohlnet.ru>
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the "Software"),
* to deal in the Software without restriction, including without limitation the
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
* sell copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included
* in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
* OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
* DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
* ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
* DEALINGS IN THE SOFTWARE.
*/
#pragma once
#ifndef FILE_AND_MEM_READER_HHHH
#define FILE_AND_MEM_READER_HHHH
#include <string> // std::string
#include <cstdio> // std::fopen, std::fread, std::fseek, std::ftell, std::fclose, std::feof
#include <stdint.h> // uint*_t
#include <stddef.h> // size_t and friends
#ifdef _WIN32
#define NOMINMAX 1
#include <cstring> // std::strlen
#include <windows.h> // MultiByteToWideChar
#endif
/**
* @brief A little class gives able to read filedata from disk and also from a memory segment
*/
class FileAndMemReader
{
//! Currently loaded filename (empty for a memory blocks)
std::string m_file_name;
//! File reader descriptor
std::FILE *m_fp;
//! Memory pointer descriptor
const void *m_mp;
//! Size of memory block
size_t m_mp_size;
//! Cursor position in the memory block
size_t m_mp_tell;
public:
/**
* @brief Relation direction
*/
enum relTo
{
//! At begin position
SET = SEEK_SET,
//! At current position
CUR = SEEK_CUR,
//! At end position
END = SEEK_END
};
/**
* @brief C.O.: It's a constructor!
*/
FileAndMemReader() :
m_fp(NULL),
m_mp(NULL),
m_mp_size(0),
m_mp_tell(0)
{}
/**
* @brief C.O.: It's a destructor!
*/
~FileAndMemReader()
{
close();
}
/**
* @brief Open file from a disk
* @param path Path to the file in UTF-8 (even on Windows!)
*/
void openFile(const char *path)
{
if(m_fp)
this->close();//Close previously opened file first!
#if !defined(_WIN32) || defined(__WATCOMC__)
m_fp = std::fopen(path, "rb");
#else
wchar_t widePath[MAX_PATH];
int size = MultiByteToWideChar(CP_UTF8, 0, path, static_cast<int>(std::strlen(path)), widePath, MAX_PATH);
widePath[size] = '\0';
m_fp = _wfopen(widePath, L"rb");
#endif
m_file_name = path;
m_mp = NULL;
m_mp_size = 0;
m_mp_tell = 0;
}
/**
* @brief Open file from memory block
* @param mem Pointer to the memory block
* @param lenght Size of given block
*/
void openData(const void *mem, size_t lenght)
{
if(m_fp)
this->close();//Close previously opened file first!
m_fp = NULL;
m_mp = mem;
m_mp_size = lenght;
m_mp_tell = 0;
}
/**
* @brief Seek to given position
* @param pos Offset or position
* @param rel_to Relation (at begin, at current, or at end)
*/
void seek(long pos, int rel_to)
{
if(!this->isValid())
return;
if(m_fp)//If a file
{
std::fseek(m_fp, pos, rel_to);
}
else//If a memory block
{
switch(rel_to)
{
case SET:
m_mp_tell = static_cast<size_t>(pos);
break;
case END:
m_mp_tell = m_mp_size - static_cast<size_t>(pos);
break;
case CUR:
m_mp_tell = m_mp_tell + static_cast<size_t>(pos);
break;
}
if(m_mp_tell > m_mp_size)
m_mp_tell = m_mp_size;
}
}
/**
* @brief Seek to given position (unsigned integer 64 as relation. Negative values not supported)
* @param pos Offset or position
* @param rel_to Relation (at begin, at current, or at end)
*/
inline void seeku(uint64_t pos, int rel_to)
{
this->seek(static_cast<long>(pos), rel_to);
}
/**
* @brief Read the buffer from a file
* @param buf Pointer to the destination memory block
* @param num Number of elements
* @param size Size of one element
* @return Size
*/
size_t read(void *buf, size_t num, size_t size)
{
if(!this->isValid())
return 0;
if(m_fp)
return std::fread(buf, num, size, m_fp);
else
{
size_t pos = 0;
size_t maxSize = static_cast<size_t>(size * num);
while((pos < maxSize) && (m_mp_tell < m_mp_size))
{
reinterpret_cast<uint8_t *>(buf)[pos] = reinterpret_cast<const uint8_t *>(m_mp)[m_mp_tell];
m_mp_tell++;
pos++;
}
return pos / num;
}
}
/**
* @brief Get one byte and seek forward
* @return Readed byte or EOF (a.k.a. -1)
*/
int getc()
{
if(!this->isValid())
return -1;
if(m_fp)//If a file
{
return std::getc(m_fp);
}
else //If a memory block
{
if(m_mp_tell >= m_mp_size)
return -1;
int x = reinterpret_cast<const uint8_t *>(m_mp)[m_mp_tell];
m_mp_tell++;
return x;
}
}
/**
* @brief Returns current offset of cursor in a file
* @return Offset position
*/
size_t tell()
{
if(!this->isValid())
return 0;
if(m_fp)//If a file
return static_cast<size_t>(std::ftell(m_fp));
else//If a memory block
return m_mp_tell;
}
/**
* @brief Close the file
*/
void close()
{
if(m_fp)
std::fclose(m_fp);
m_fp = NULL;
m_mp = NULL;
m_mp_size = 0;
m_mp_tell = 0;
}
/**
* @brief Is file instance valid
* @return true if vaild
*/
bool isValid()
{
return (m_fp) || (m_mp);
}
/**
* @brief Is End Of File?
* @return true if end of file was reached
*/
bool eof()
{
if(!this->isValid())
return true;
if(m_fp)
return (std::feof(m_fp) != 0);
else
return m_mp_tell >= m_mp_size;
}
/**
* @brief Get a current file name
* @return File name of currently loaded file
*/
const std::string &fileName()
{
return m_file_name;
}
/**
* @brief Retrieve file size
* @return Size of file in bytes
*/
size_t fileSize()
{
if(!this->isValid())
return 0;
if(!m_fp)
return m_mp_size; //Size of memory block is well known
size_t old_pos = this->tell();
seek(0l, FileAndMemReader::END);
size_t file_size = this->tell();
seek(static_cast<long>(old_pos), FileAndMemReader::SET);
return file_size;
}
};
#endif /* FILE_AND_MEM_READER_HHHH */

View file

@ -1,215 +0,0 @@
#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);
};
#ifdef _MSC_VER
#pragma warning(disable:4146)
#endif
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());
}
#ifdef _MSC_VER
#pragma warning(default:4146)
#endif
template<typename inttype>
inline const fraction<inttype> abs(const fraction<inttype> &f)
{
return fraction<inttype>(abs(f.nom()), abs(f.denom()));
}
#define fraction_blah_func(op) \
template<typename inttype> \
fraction<inttype> operator op \
(const inttype bla, const fraction<inttype> &b) \
{ return fraction<inttype> (bla) op b; }
fraction_blah_func( + )
fraction_blah_func( - )
fraction_blah_func( * )
fraction_blah_func( / )
#undef fraction_blah_func
#define fraction_blah_func(op1, op2) \
template<typename inttype> \
fraction<inttype> &fraction<inttype>::operator op2 (const fraction<inttype> &b) \
{ \
inttype newnom = nom()*b.denom() op1 denom()*b.nom(); \
num2 *= b.denom(); \
num1 = newnom; \
Optim(); \
return *this; \
}
fraction_blah_func( +, += )
fraction_blah_func( -, -= )
#undef fraction_blah_func
template<typename inttype>
fraction<inttype> &fraction<inttype>::operator= (long double orig)
{
if(orig == 0.0)
{
set(0, 0);
return *this;
}
inttype cf[25];
for(int maxdepth=1; maxdepth<25; ++maxdepth)
{
inttype u,v;
long double virhe, a=orig;
int i, viim;
for(i = 0; i < maxdepth; ++i)
{
cf[i] = (inttype)a;
if(cf[i]-1 > cf[i])break;
a = 1.0 / (a - cf[i]);
}
for(viim=i-1; i < maxdepth; ++i)
cf[i] = 0;
u = cf[viim];
v = 1;
for(i = viim-1; i >= 0; --i)
{
inttype w = cf[i] * u + v;
v = u;
u = w;
}
virhe = (orig - (u / (long double)v)) / orig;
set(u, v);
//if(verbose > 4)
// cerr << "Guess: " << *this << " - error = " << virhe*100 << "%\n";
if(virhe < 1e-8 && virhe > -1e-8)break;
}
//if(verbose > 4)
//{
// cerr << "Fraction=" << orig << ": " << *this << endl;
//}
return *this;
}
/*
template<typename inttype>
ostream &operator << (ostream &dest, const fraction<inttype> &m)
{
if(m.denom() == (inttype)1) return dest << m.nom();
return dest << m.nom() << '/' << m.denom();
}
*/
#endif

View file

@ -353,6 +353,8 @@ int WOPL_LoadInstFromMem(WOPIFile *file, void *mem, size_t length)
GO_FORWARD(2);
}
file->version = version;
{/* is drum flag */
if(length < 1)
return WOPL_ERR_UNEXPECTED_ENDING;
@ -434,11 +436,13 @@ size_t WOPL_CalculateInstFileSize(WOPIFile *file, uint16_t version)
* is percussive instrument
*/
if(version >= 3)
ins_size = WOPL_INST_SIZE_V3;
if(version > 2)
/* Skip sounding delays are not part of single-instrument file
* two sizes of uint16_t will be subtracted */
ins_size = WOPL_INST_SIZE_V3 - (sizeof(uint16_t) * 2);
else
ins_size = WOPL_INST_SIZE_V2;
final_size += ins_size * 128;
final_size += ins_size;
return final_size;
}

View file

@ -71,13 +71,13 @@ typedef enum WOPL_InstrumentFlags
WOPL_Ins_IsBlank = 0x04,
/* RythmMode flags mask */
WOPL_RythmModeMask = 0x38,
WOPL_RhythmModeMask = 0x38,
/* Mask of the flags range */
WOPL_Ins_ALL_MASK = 0x07
} WOPL_InstrumentFlags;
typedef enum WOPL_RythmMode
typedef enum WOPL_RhythmMode
{
/* RythmMode: BassDrum */
WOPL_RM_BassDrum = 0x08,
@ -86,10 +86,13 @@ typedef enum WOPL_RythmMode
/* RythmMode: TomTom */
WOPL_RM_TomTom = 0x18,
/* RythmMode: Cymbell */
WOPL_RM_Cymball = 0x20,
WOPL_RM_Cymbal = 0x20,
/* RythmMode: HiHat */
WOPL_RM_HiHat = 0x28
} WOPL_RythmMode;
} WOPL_RhythmMode;
/* DEPRECATED: It has typo. Don't use it! */
typedef WOPL_RhythmMode WOPL_RythmMode;
/* Error codes */
typedef enum WOPL_ErrorCodes

View file

@ -331,6 +331,9 @@ protected:
void HandleEvent(int status, int parm1, int parm2);
void HandleLongEvent(const uint8_t *data, int len);
void ComputeOutput(float *buffer, int len);
private:
int LoadCustomBank(const char *bankfile);
};
@ -341,14 +344,17 @@ public:
OPNMIDIDevice(const char *args);
~OPNMIDIDevice();
int Open(MidiCallback, void *userdata);
int GetDeviceType() const override { return MDEV_OPN; }
protected:
void HandleEvent(int status, int parm1, int parm2);
void HandleLongEvent(const uint8_t *data, int len);
void ComputeOutput(float *buffer, int len);
private:
int LoadCustomBank(const char *bankfile);
};

View file

@ -302,6 +302,16 @@ void FSoundFontManager::ProcessOneFile(const FString &fn)
FSoundFontInfo sft = { fb, fbe, fn, SF_SF2 };
soundfonts.Push(sft);
}
if (!memcmp(head, "WOPL3-BANK\0", 11))
{
FSoundFontInfo sft = { fb, fbe, fn, SF_WOPL };
soundfonts.Push(sft);
}
if (!memcmp(head, "WOPN2-BANK\0", 11) || !memcmp(head, "WOPN2-B2NK\0", 11))
{
FSoundFontInfo sft = { fb, fbe, fn, SF_WOPN };
soundfonts.Push(sft);
}
else if (!memcmp(head, "PK", 2))
{
auto zip = FResourceFile::OpenResourceFile(fn, true);
@ -334,7 +344,6 @@ void FSoundFontManager::CollectSoundfonts()
findstate_t c_file;
void *file;
if (GameConfig != NULL && GameConfig->SetSection ("SoundfontSearch.Directories"))
{
const char *key;

View file

@ -7,7 +7,9 @@
enum
{
SF_SF2 = 1,
SF_GUS = 2
SF_GUS = 2,
SF_WOPL = 4,
SF_WOPN = 8
};
struct FSoundFontInfo

View file

@ -36,6 +36,7 @@
#include "i_musicinterns.h"
#include "adlmidi/adlmidi.h"
#include "i_soundfont.h"
enum
{
@ -72,6 +73,15 @@ CUSTOM_CVAR(Bool, adl_run_at_pcm_rate, 0, CVAR_ARCHIVE | CVAR_GLOBALCONFIG)
}
}
CUSTOM_CVAR(Bool, adl_fullpan, 1, CVAR_ARCHIVE | CVAR_GLOBALCONFIG)
{
if (currSong != nullptr && currSong->GetDeviceType() == MDEV_ADL)
{
MIDIDeviceChanged(-1, true);
}
}
CUSTOM_CVAR(Int, adl_bank, 14, CVAR_ARCHIVE | CVAR_GLOBALCONFIG)
{
if (currSong != nullptr && currSong->GetDeviceType() == MDEV_ADL)
@ -118,18 +128,11 @@ ADLMIDIDevice::ADLMIDIDevice(const char *args)
{
adl_switchEmulator(Renderer, (int)adl_emulator_id);
adl_setRunAtPcmRate(Renderer, (int)adl_run_at_pcm_rate);
// todo: Implement handling of external or in-resources WOPL bank files and load
/*
if(adl_use_custom_bank)
{
adl_openBankFile(Renderer, (char*)adl_bank_file);
adl_openBankData(Renderer, (char*)adl_bank, (unsigned long)size);
}
else
*/
if(!LoadCustomBank(adl_custom_bank))
adl_setBank(Renderer, (int)adl_bank);
adl_setNumChips(Renderer, (int)adl_chips_count);
adl_setVolumeRangeModel(Renderer, (int)adl_volume_model);
adl_setSoftPanEnabled(Renderer, (int)adl_fullpan);
}
}
@ -148,6 +151,27 @@ ADLMIDIDevice::~ADLMIDIDevice()
}
}
//==========================================================================
//
// ADLMIDIDevice :: LoadCustomBank
//
// Loads a custom WOPL bank for libADLMIDI. Returns 1 when bank has been
// loaded, otherwise, returns 0 when custom banks are disabled or failed
//
//==========================================================================
int ADLMIDIDevice::LoadCustomBank(const char *bankfile)
{
if(!adl_use_custom_bank)
return 0;
auto info = sfmanager.FindSoundFont(bankfile, SF_WOPL);
if(info == nullptr)
return 0;
bankfile = info->mFilename.GetChars();
return (adl_openBankFile(Renderer, bankfile) == 0);
}
//==========================================================================
//
// ADLMIDIDevice :: Open

View file

@ -38,6 +38,7 @@
#include "w_wad.h"
#include "i_system.h"
#include "opnmidi/opnmidi.h"
#include "i_soundfont.h"
enum
{
@ -74,6 +75,14 @@ CUSTOM_CVAR(Bool, opn_run_at_pcm_rate, 0, CVAR_ARCHIVE | CVAR_GLOBALCONFIG)
}
}
CUSTOM_CVAR(Bool, opn_fullpan, 1, CVAR_ARCHIVE | CVAR_GLOBALCONFIG)
{
if (currSong != nullptr && currSong->GetDeviceType() == MDEV_OPN)
{
MIDIDeviceChanged(-1, true);
}
}
CUSTOM_CVAR(Bool, opn_use_custom_bank, 0, CVAR_ARCHIVE | CVAR_GLOBALCONFIG)
{
if (currSong != nullptr && currSong->GetDeviceType() == MDEV_OPN)
@ -102,17 +111,8 @@ OPNMIDIDevice::OPNMIDIDevice(const char *args)
Renderer = opn2_init(44100); // todo: make it configurable
if (Renderer != nullptr)
{
opn2_switchEmulator(Renderer, (int)opn_emulator_id);
opn2_setRunAtPcmRate(Renderer, (int)opn_run_at_pcm_rate);
// todo: Implement handling of external or in-resources WOPN bank files and load
/*
if(opn_use_custom_bank)
if (!LoadCustomBank(opn_custom_bank))
{
opn2_openBankFile(Renderer, (char*)opn_bank_file);
opn2_openBankData(Renderer, (char*)opn_bank, (long)size);
}
else
*/
int lump = Wads.CheckNumForFullName("xg.wopn");
if (lump < 0)
{
@ -120,7 +120,12 @@ OPNMIDIDevice::OPNMIDIDevice(const char *args)
}
FMemLump data = Wads.ReadLump(lump);
opn2_openBankData(Renderer, data.GetMem(), (long)data.GetSize());
}
opn2_switchEmulator(Renderer, (int)opn_emulator_id);
opn2_setRunAtPcmRate(Renderer, (int)opn_run_at_pcm_rate);
opn2_setNumChips(Renderer, opn_chips_count);
opn2_setSoftPanEnabled(Renderer, (int)opn_fullpan);
}
}
@ -139,6 +144,27 @@ OPNMIDIDevice::~OPNMIDIDevice()
}
}
//==========================================================================
//
// OPNMIDIDevice :: LoadCustomBank
//
// Loads a custom WOPN bank for libOPNMIDI. Returns 1 when bank has been
// loaded, otherwise, returns 0 when custom banks are disabled or failed
//
//==========================================================================
int OPNMIDIDevice::LoadCustomBank(const char *bankfile)
{
if(!opn_use_custom_bank)
return 0;
auto info = sfmanager.FindSoundFont(bankfile, SF_WOPN);
if(info == nullptr)
return 0;
bankfile = info->mFilename.GetChars();
return (opn2_openBankFile(Renderer, bankfile) == 0);
}
//==========================================================================
//
// OPNMIDIDevice :: Open

View file

@ -1,4 +1,4 @@
// Game_Music_Emu 0.6.0. http://www.slack.net/~ant/
// Game_Music_Emu 0.6.0. http://www.slack.net/~ant/
// Based on Gens 2.10 ym2612.c
@ -96,6 +96,8 @@ struct channel_t
int KC[4]; // Key Code = valeur fonction de la frequence (voir KSR pour les slots, KSR = KC >> KSR_S)
slot_t SLOT[4]; // four slot.operators = les 4 slots de la voie
int FFlag; // Frequency step recalculation flag
int PANVolumeL; // Left PCM output channel volume
int PANVolumeR; // Right PCM output channel volume
};
struct state_t
@ -253,6 +255,32 @@ static const unsigned char LFO_FMS_TAB [8] =
LFO_FMS_BASE * 12, LFO_FMS_BASE * 24
};
/*
* Pan law table
*/
static const unsigned short panlawtable[] =
{
65535, 65529, 65514, 65489, 65454, 65409, 65354, 65289,
65214, 65129, 65034, 64929, 64814, 64689, 64554, 64410,
64255, 64091, 63917, 63733, 63540, 63336, 63123, 62901,
62668, 62426, 62175, 61914, 61644, 61364, 61075, 60776,
60468, 60151, 59825, 59489, 59145, 58791, 58428, 58057,
57676, 57287, 56889, 56482, 56067, 55643, 55211, 54770,
54320, 53863, 53397, 52923, 52441, 51951, 51453, 50947,
50433, 49912, 49383, 48846, 48302, 47750, 47191,
46340, /* Center left */
46340, /* Center right */
45472, 44885, 44291, 43690, 43083, 42469, 41848, 41221,
40588, 39948, 39303, 38651, 37994, 37330, 36661, 35986,
35306, 34621, 33930, 33234, 32533, 31827, 31116, 30400,
29680, 28955, 28225, 27492, 26754, 26012, 25266, 24516,
23762, 23005, 22244, 21480, 20713, 19942, 19169, 18392,
17613, 16831, 16046, 15259, 14469, 13678, 12884, 12088,
11291, 10492, 9691, 8888, 8085, 7280, 6473, 5666,
4858, 4050, 3240, 2431, 1620, 810, 0
};
inline void YM2612_Special_Update() { }
struct Ym2612_Impl
@ -273,6 +301,7 @@ struct Ym2612_Impl
void reset();
void write0( int addr, int data );
void write1( int addr, int data );
void write_pan(int channel, int data );
void run_timer( int );
void run( int pair_count, Ym2612_Emu::sample_t* );
};
@ -880,6 +909,12 @@ inline void Ym2612_Impl::write1( int opn_addr, int data )
}
}
void Ym2612_Impl::write_pan( int channel, int data )
{
YM2612.CHANNEL[channel].PANVolumeL = panlawtable[data & 0x7F];
YM2612.CHANNEL[channel].PANVolumeR = panlawtable[0x7F - (data & 0x7F)];
}
void Ym2612_Emu::reset()
{
impl->reset();
@ -910,6 +945,9 @@ void Ym2612_Impl::reset()
ch.FMS = 0;
ch.AMS = 0;
ch.PANVolumeL = 46340;
ch.PANVolumeR = 46340;
for ( int j = 0 ;j < 4 ; j++ )
{
ch.S0_OUT [j] = 0;
@ -959,6 +997,11 @@ void Ym2612_Emu::write1( int addr, int data )
impl->write1( addr, data );
}
void Ym2612_Emu::write_pan(int channel, int data)
{
impl->write_pan( channel, data );
}
void Ym2612_Emu::mute_voices( int mask ) { impl->mute_mask = mask; }
static void update_envelope_( slot_t* sl )
@ -1167,8 +1210,8 @@ void ym2612_update_chan<algo>::func( tables_t& g, channel_t& ch,
in2 += (ch.SLOT [S2].Finc * freq_LFO) >> (LFO_FMS_LBITS - 1);
in3 += (ch.SLOT [S3].Finc * freq_LFO) >> (LFO_FMS_LBITS - 1);
int t0 = buf [0] + (CH_OUTd & ch.LEFT);
int t1 = buf [1] + (CH_OUTd & ch.RIGHT);
int t0 = buf [0] + ((CH_OUTd * ch.PANVolumeL / 65535) & ch.LEFT);
int t1 = buf [1] + ((CH_OUTd * ch.PANVolumeR / 65535) & ch.RIGHT);
update_envelope( ch.SLOT [0] );
update_envelope( ch.SLOT [1] );

View file

@ -29,6 +29,9 @@ public:
// Write addr to register 2 then data to register 3
void write1( int addr, int data );
// Write pan level channel data
void write_pan( int channel, int data );
// Run and add pair_count samples into current output buffer contents
typedef short sample_t;
enum { out_chan_count = 2 }; // stereo

View file

@ -1,3 +1,23 @@
/*
* Interfaces over Yamaha OPN2 (YM2612) chip emulators
*
* Copyright (C) 2017-2018 Vitaly Novichkov (Wohlstand)
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
#include "gens_opn2.h"
#include <cstring>
@ -40,6 +60,11 @@ void GensOPN2::writeReg(uint32_t port, uint16_t addr, uint8_t data)
}
}
void GensOPN2::writePan(uint16_t chan, uint8_t data)
{
chip->write_pan(static_cast<int>(chan), static_cast<int>(data));
}
void GensOPN2::nativeGenerateN(int16_t *output, size_t frames)
{
std::memset(output, 0, frames * sizeof(int16_t) * 2);

View file

@ -1,3 +1,23 @@
/*
* Interfaces over Yamaha OPN2 (YM2612) chip emulators
*
* Copyright (C) 2017-2018 Vitaly Novichkov (Wohlstand)
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
#ifndef GENS_OPN2_H
#define GENS_OPN2_H
@ -15,6 +35,7 @@ public:
void setRate(uint32_t rate, uint32_t clock) override;
void reset() override;
void writeReg(uint32_t port, uint16_t addr, uint8_t data) override;
void writePan(uint16_t chan, uint8_t data) override;
void nativePreGenerate() override {}
void nativePostGenerate() override {}
void nativeGenerateN(int16_t *output, size_t frames) override;

View file

@ -136,6 +136,7 @@
#include <stdlib.h>
#include <string.h> /* for memset */
#include <stddef.h> /* for NULL */
#include <assert.h>
#include <math.h>
#include "mamedef.h"
#include "mame_ym2612fm.h"
@ -543,6 +544,33 @@ static FILE *sample[1];
#endif
/*
* Pan law table
*/
static const UINT16 panlawtable[] =
{
65535, 65529, 65514, 65489, 65454, 65409, 65354, 65289,
65214, 65129, 65034, 64929, 64814, 64689, 64554, 64410,
64255, 64091, 63917, 63733, 63540, 63336, 63123, 62901,
62668, 62426, 62175, 61914, 61644, 61364, 61075, 60776,
60468, 60151, 59825, 59489, 59145, 58791, 58428, 58057,
57676, 57287, 56889, 56482, 56067, 55643, 55211, 54770,
54320, 53863, 53397, 52923, 52441, 51951, 51453, 50947,
50433, 49912, 49383, 48846, 48302, 47750, 47191,
46340, /* Center left */
46340, /* Center right */
45472, 44885, 44291, 43690, 43083, 42469, 41848, 41221,
40588, 39948, 39303, 38651, 37994, 37330, 36661, 35986,
35306, 34621, 33930, 33234, 32533, 31827, 31116, 30400,
29680, 28955, 28225, 27492, 26754, 26012, 25266, 24516,
23762, 23005, 22244, 21480, 20713, 19942, 19169, 18392,
17613, 16831, 16046, 15259, 14469, 13678, 12884, 12088,
11291, 10492, 9691, 8888, 8085, 7280, 6473, 5666,
4858, 4050, 3240, 2431, 1620, 810, 0
};
/* struct describing a single operator (SLOT) */
typedef struct
{
@ -608,6 +636,9 @@ typedef struct
UINT8 kcode; /* key code: */
UINT32 block_fnum; /* current blk/fnum value for this slot (can be different betweeen slots of one channel in 3slot mode) */
UINT8 Muted;
INT32 pan_volume_l;
INT32 pan_volume_r;
} FM_CH;
@ -2457,34 +2488,40 @@ void ym2612_generate_one_native(void *chip, FMSAMPLE buffer[])
if (out_fm[5] > 8192) out_fm[5] = 8192;
else if (out_fm[5] < -8192) out_fm[5] = -8192;
#define PANLAW_L(ch, chpan) (((out_fm[ch]>>0) * cch[ch].pan_volume_l / 65535) & OPN->pan[chpan]);
#define PANLAW_R(ch, chpan) (((out_fm[ch]>>0) * cch[ch].pan_volume_r / 65535) & OPN->pan[chpan]);
/* 6-channels mixing */
lt = ((out_fm[0]>>0) & OPN->pan[0]);
rt = ((out_fm[0]>>0) & OPN->pan[1]);
lt += ((out_fm[1]>>0) & OPN->pan[2]);
rt += ((out_fm[1]>>0) & OPN->pan[3]);
lt += ((out_fm[2]>>0) & OPN->pan[4]);
rt += ((out_fm[2]>>0) & OPN->pan[5]);
lt += ((out_fm[3]>>0) & OPN->pan[6]);
rt += ((out_fm[3]>>0) & OPN->pan[7]);
lt = PANLAW_L(0, 0);
rt = PANLAW_R(0, 1);
lt += PANLAW_L(1, 2);
rt += PANLAW_R(1, 3);
lt += PANLAW_L(2, 4);
rt += PANLAW_R(2, 5);
lt += PANLAW_L(3, 6);
rt += PANLAW_R(3, 7);
if (! F2612->dac_test)
{
lt += ((out_fm[4]>>0) & OPN->pan[8]);
rt += ((out_fm[4]>>0) & OPN->pan[9]);
lt += PANLAW_L(4, 8);
rt += PANLAW_R(4, 9);
}
else
{
lt += dacout;
lt += dacout;
rt += dacout;
}
lt += ((out_fm[5]>>0) & OPN->pan[10]);
rt += ((out_fm[5]>>0) & OPN->pan[11]);
lt += PANLAW_L(5, 10);
rt += PANLAW_R(5, 11);
#undef PANLAW_L
#undef PANLAW_R
/* Limit( lt, MAXOUT, MINOUT ); */
/* Limit( rt, MAXOUT, MINOUT ); */
#ifdef SAVE_SAMPLE
#ifdef SAVE_SAMPLE
SAVE_ALL_CHANNELS
#endif
#endif
/* buffering */
if (F2612->WaveOutMode & 0x01)
@ -2587,6 +2624,7 @@ void * ym2612_init(void *param, int clock, int rate,
FM_TIMERHANDLER timer_handler,FM_IRQHANDLER IRQHandler)
{
YM2612 *F2612;
int i = 0;
if (clock <= 0 || rate <= 0)
return NULL; /* Forbid zero clock and sample rate */
@ -2624,6 +2662,13 @@ void * ym2612_init(void *param, int clock, int rate,
F2612->WaveOutMode = 0x01;
else
F2612->WaveOutMode = 0x03;
for (i = 0; i < 6; i++)
{
F2612->CH[i].pan_volume_l = 46340;
F2612->CH[i].pan_volume_r = 46340;
}
/*hFile = fopen("YM2612.log", "wt");
fprintf(hFile, "Clock: %d, Sample Rate: %d\n", clock, rate);
fprintf(hFile, "Sample\tCh 0\tCh 1\tCh 2\tCh 3\tCh 4\tCh 5\n");
@ -2786,6 +2831,14 @@ int ym2612_write(void *chip, int a, UINT8 v)
return F2612->OPN.ST.irq;
}
void ym2612_write_pan(void *chip, int c, unsigned char v)
{
YM2612 *F2612 = (YM2612 *)chip;
assert((c >= 0) && (c < 6));
F2612->CH[c].pan_volume_l = panlawtable[v & 0x7F];
F2612->CH[c].pan_volume_r = panlawtable[0x7F - (v & 0x7F)];
}
UINT8 ym2612_read(void *chip,int a)
{
YM2612 *F2612 = (YM2612 *)chip;

View file

@ -148,8 +148,9 @@ void ym2612_generate_one_native(void *chip, FMSAMPLE buffer[2]);
/* void ym2612_post_generate(void *chip, int length); */
int ym2612_write(void *chip, int a,unsigned char v);
unsigned char ym2612_read(void *chip,int a);
int ym2612_write(void *chip, int a, unsigned char v);
void ym2612_write_pan(void *chip, int c, unsigned char v);
unsigned char ym2612_read(void *chip, int a);
int ym2612_timer_over(void *chip, int c );
void ym2612_postload(void *chip);

View file

@ -1,3 +1,23 @@
/*
* Interfaces over Yamaha OPN2 (YM2612) chip emulators
*
* Copyright (C) 2017-2018 Vitaly Novichkov (Wohlstand)
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
#include "mame_opn2.h"
#include "mame/mame_ym2612fm.h"
#include <cstdlib>
@ -36,6 +56,11 @@ void MameOPN2::writeReg(uint32_t port, uint16_t addr, uint8_t data)
ym2612_write(chip, 1 + (int)(port) * 2, data);
}
void MameOPN2::writePan(uint16_t chan, uint8_t data)
{
ym2612_write_pan(chip, (int)chan, data);
}
void MameOPN2::nativePreGenerate()
{
void *chip = this->chip;

View file

@ -1,3 +1,23 @@
/*
* Interfaces over Yamaha OPN2 (YM2612) chip emulators
*
* Copyright (C) 2017-2018 Vitaly Novichkov (Wohlstand)
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
#ifndef MAME_OPN2_H
#define MAME_OPN2_H
@ -14,6 +34,7 @@ public:
void setRate(uint32_t rate, uint32_t clock) override;
void reset() override;
void writeReg(uint32_t port, uint16_t addr, uint8_t data) override;
void writePan(uint16_t chan, uint8_t data) override;
void nativePreGenerate() override;
void nativePostGenerate() override {}
void nativeGenerate(int16_t *frame) override;

View file

@ -1,19 +1,19 @@
/*
* Copyright (C) 2017 Alexey Khokholov (Nuke.YKT)
*
* 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 2
* of the License, or (at your option) any later version.
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* 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 General Public License for more details.
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*
*
* Nuked OPN2(Yamaha YM3438) emulator.
@ -216,6 +216,32 @@ static const Bit32u fm_algorithm[4][6][8] = {
}
};
/*
* Pan law table
*/
static const Bit16u panlawtable[] =
{
65535, 65529, 65514, 65489, 65454, 65409, 65354, 65289,
65214, 65129, 65034, 64929, 64814, 64689, 64554, 64410,
64255, 64091, 63917, 63733, 63540, 63336, 63123, 62901,
62668, 62426, 62175, 61914, 61644, 61364, 61075, 60776,
60468, 60151, 59825, 59489, 59145, 58791, 58428, 58057,
57676, 57287, 56889, 56482, 56067, 55643, 55211, 54770,
54320, 53863, 53397, 52923, 52441, 51951, 51453, 50947,
50433, 49912, 49383, 48846, 48302, 47750, 47191,
46340, /* Center left */
46340, /* Center right */
45472, 44885, 44291, 43690, 43083, 42469, 41848, 41221,
40588, 39948, 39303, 38651, 37994, 37330, 36661, 35986,
35306, 34621, 33930, 33234, 32533, 31827, 31116, 30400,
29680, 28955, 28225, 27492, 26754, 26012, 25266, 24516,
23762, 23005, 22244, 21480, 20713, 19942, 19169, 18392,
17613, 16831, 16046, 15259, 14469, 13678, 12884, 12088,
11291, 10492, 9691, 8888, 8085, 7280, 6473, 5666,
4858, 4050, 3240, 2431, 1620, 810, 0
};
static Bit32u chip_type = ym3438_type_discrete;
void OPN2_DoIO(ym3438_t *chip)
@ -1204,6 +1230,8 @@ void OPN2_Reset(ym3438_t *chip, Bit32u rate, Bit32u clock)
{
chip->pan_l[i] = 1;
chip->pan_r[i] = 1;
chip->pan_volume_l[i] = 46340;
chip->pan_volume_r[i] = 46340;
}
if (rate != 0)
@ -1426,6 +1454,13 @@ Bit8u OPN2_Read(ym3438_t *chip, Bit32u port)
return 0;
}
void OPN2_WritePan(ym3438_t *chip, Bit32u channel, Bit8u data)
{
chip->pan_volume_l[channel] = panlawtable[data & 0x7F];
chip->pan_volume_r[channel] = panlawtable[0x7F - (data & 0x7F)];
}
void OPN2_WriteBuffered(ym3438_t *chip, Bit32u port, Bit8u data)
{
Bit64u time1, time2;
@ -1466,6 +1501,7 @@ void OPN2_Generate(ym3438_t *chip, Bit16s *buf)
Bit32u i;
Bit16s buffer[2];
Bit32u mute;
Bit32s channel = -1;
buf[0] = 0;
buf[1] = 0;
@ -1476,21 +1512,27 @@ void OPN2_Generate(ym3438_t *chip, Bit16s *buf)
{
case 0: /* Ch 2 */
mute = chip->mute[1];
channel = 1;
break;
case 1: /* Ch 6, DAC */
mute = chip->mute[5 + chip->dacen];
channel = 5;
break;
case 2: /* Ch 4 */
mute = chip->mute[3];
channel = 3;
break;
case 3: /* Ch 1 */
mute = chip->mute[0];
channel = 0;
break;
case 4: /* Ch 5 */
mute = chip->mute[4];
channel = 4;
break;
case 5: /* Ch 3 */
mute = chip->mute[2];
channel = 2;
break;
default:
mute = 0;
@ -1499,6 +1541,11 @@ void OPN2_Generate(ym3438_t *chip, Bit16s *buf)
OPN2_Clock(chip, buffer);
if (!mute)
{
if (channel >= 0)
{
buffer[0] = buffer[0] * chip->pan_volume_l[channel] / 65535;
buffer[1] = buffer[1] * chip->pan_volume_r[channel] / 65535;
}
buf[0] += buffer[0];
buf[1] += buffer[1];
}

View file

@ -1,19 +1,19 @@
/*
* Copyright (C) 2017 Alexey Khokholov (Nuke.YKT)
*
* 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 2
* of the License, or (at your option) any later version.
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* 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 General Public License for more details.
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*
*
* Nuked OPN2(Yamaha YM3438) emulator.
@ -213,6 +213,9 @@ typedef struct
Bit32s oldsamples[2];
Bit32s samples[2];
Bit32u pan_volume_l[6];
Bit32u pan_volume_r[6];
Bit64u writebuf_samplecnt;
Bit32u writebuf_cur;
Bit32u writebuf_last;
@ -231,6 +234,7 @@ Bit32u OPN2_ReadIRQPin(ym3438_t *chip);
Bit8u OPN2_Read(ym3438_t *chip, Bit32u port);
/*EXTRA*/
void OPN2_WritePan(ym3438_t *chip, Bit32u channel, Bit8u data);
void OPN2_WriteBuffered(ym3438_t *chip, Bit32u port, Bit8u data);
void OPN2_Generate(ym3438_t *chip, Bit16s *buf);
void OPN2_GenerateResampled(ym3438_t *chip, Bit16s *buf);

View file

@ -1,3 +1,23 @@
/*
* Interfaces over Yamaha OPN2 (YM2612) chip emulators
*
* Copyright (C) 2017-2018 Vitaly Novichkov (Wohlstand)
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
#include "nuked_opn2.h"
#include "nuked/ym3438.h"
#include <cstring>
@ -37,6 +57,12 @@ void NukedOPN2::writeReg(uint32_t port, uint16_t addr, uint8_t data)
//qDebug() << QString("%1: 0x%2 => 0x%3").arg(port).arg(addr, 2, 16, QChar('0')).arg(data, 2, 16, QChar('0'));
}
void NukedOPN2::writePan(uint16_t chan, uint8_t data)
{
ym3438_t *chip_r = reinterpret_cast<ym3438_t*>(chip);
OPN2_WritePan(chip_r, (Bit32u)chan, data);
}
void NukedOPN2::nativeGenerate(int16_t *frame)
{
ym3438_t *chip_r = reinterpret_cast<ym3438_t*>(chip);

View file

@ -1,3 +1,23 @@
/*
* Interfaces over Yamaha OPN2 (YM2612) chip emulators
*
* Copyright (C) 2017-2018 Vitaly Novichkov (Wohlstand)
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
#ifndef NUKED_OPN2_H
#define NUKED_OPN2_H
@ -14,6 +34,7 @@ public:
void setRate(uint32_t rate, uint32_t clock) override;
void reset() override;
void writeReg(uint32_t port, uint16_t addr, uint8_t data) override;
void writePan(uint16_t chan, uint8_t data) override;
void nativePreGenerate() override {}
void nativePostGenerate() override {}
void nativeGenerate(int16_t *frame) override;

View file

@ -1,3 +1,23 @@
/*
* Interfaces over Yamaha OPN2 (YM2612) chip emulators
*
* Copyright (C) 2017-2018 Vitaly Novichkov (Wohlstand)
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
#ifndef ONP_CHIP_BASE_H
#define ONP_CHIP_BASE_H
@ -13,25 +33,40 @@
class VResampler;
#endif
#if defined(OPNMIDI_AUDIO_TICK_HANDLER)
extern void opn2_audioTickHandler(void *instance, uint32_t chipId, uint32_t rate);
#endif
class OPNChipBase
{
public:
enum { nativeRate = 53267 };
protected:
uint32_t m_id;
uint32_t m_rate;
uint32_t m_clock;
public:
OPNChipBase();
virtual ~OPNChipBase();
uint32_t chipId() const { return m_id; }
void setChipId(uint32_t id) { m_id = id; }
virtual bool canRunAtPcmRate() const = 0;
virtual bool isRunningAtPcmRate() const = 0;
virtual bool setRunningAtPcmRate(bool r) = 0;
#if defined(OPNMIDI_AUDIO_TICK_HANDLER)
virtual void setAudioTickHandlerInstance(void *instance) = 0;
#endif
virtual void setRate(uint32_t rate, uint32_t clock) = 0;
virtual uint32_t effectiveRate() const = 0;
virtual void reset() = 0;
virtual void writeReg(uint32_t port, uint16_t addr, uint8_t data) = 0;
// extended
virtual void writePan(uint16_t addr, uint8_t data) { (void)addr; (void)data; }
virtual void nativePreGenerate() = 0;
virtual void nativePostGenerate() = 0;
virtual void nativeGenerate(int16_t *frame) = 0;
@ -58,8 +93,12 @@ public:
bool isRunningAtPcmRate() const override;
bool setRunningAtPcmRate(bool r) override;
#if defined(OPNMIDI_AUDIO_TICK_HANDLER)
void setAudioTickHandlerInstance(void *instance);
#endif
virtual void setRate(uint32_t rate, uint32_t clock) override;
uint32_t effectiveRate() const override;
virtual void reset() override;
void generate(int16_t *output, size_t frames) override;
void generateAndMix(int16_t *output, size_t frames) override;
@ -67,6 +106,10 @@ public:
void generateAndMix32(int32_t *output, size_t frames) override;
private:
bool m_runningAtPcmRate;
#if defined(OPNMIDI_AUDIO_TICK_HANDLER)
void *m_audioTickHandlerInstance;
#endif
void nativeTick(int16_t *frame);
void setupResampler(uint32_t rate);
void resetResampler();
void resampledGenerate(int32_t *output);

View file

@ -20,6 +20,7 @@
/* OPNChipBase */
inline OPNChipBase::OPNChipBase() :
m_id(0),
m_rate(44100),
m_clock(7670454)
{
@ -35,6 +36,10 @@ template <class T>
OPNChipBaseT<T>::OPNChipBaseT()
: OPNChipBase(),
m_runningAtPcmRate(false)
#if defined(OPNMIDI_AUDIO_TICK_HANDLER)
,
m_audioTickHandlerInstance(NULL)
#endif
{
#if defined(OPNMIDI_ENABLE_HQ_RESAMPLER)
m_resampler = new VResampler;
@ -69,6 +74,14 @@ bool OPNChipBaseT<T>::setRunningAtPcmRate(bool r)
return true;
}
#if defined(OPNMIDI_AUDIO_TICK_HANDLER)
template <class T>
void OPNChipBaseT<T>::setAudioTickHandlerInstance(void *instance)
{
m_audioTickHandlerInstance = instance;
}
#endif
template <class T>
void OPNChipBaseT<T>::setRate(uint32_t rate, uint32_t clock)
{
@ -81,6 +94,12 @@ void OPNChipBaseT<T>::setRate(uint32_t rate, uint32_t clock)
resetResampler();
}
template <class T>
uint32_t OPNChipBaseT<T>::effectiveRate() const
{
return m_runningAtPcmRate ? m_rate : (uint32_t)nativeRate;
}
template <class T>
void OPNChipBaseT<T>::reset()
{
@ -152,6 +171,15 @@ void OPNChipBaseT<T>::generateAndMix32(int32_t *output, size_t frames)
static_cast<T *>(this)->nativePostGenerate();
}
template <class T>
void OPNChipBaseT<T>::nativeTick(int16_t *frame)
{
#if defined(OPNMIDI_AUDIO_TICK_HANDLER)
opn2_audioTickHandler(m_audioTickHandlerInstance, m_id, effectiveRate());
#endif
static_cast<T *>(this)->nativeGenerate(frame);
}
template <class T>
void OPNChipBaseT<T>::setupResampler(uint32_t rate)
{
@ -184,7 +212,7 @@ void OPNChipBaseT<T>::resampledGenerate(int32_t *output)
if(UNLIKELY(m_runningAtPcmRate))
{
int16_t in[2];
static_cast<T *>(this)->nativeGenerate(in);
static_cast<T *>(this)->nativeTick(in);
output[0] = (int32_t)in[0] * T::resamplerPreAmplify / T::resamplerPostAttenuate;
output[1] = (int32_t)in[1] * T::resamplerPreAmplify / T::resamplerPostAttenuate;
return;
@ -202,7 +230,7 @@ void OPNChipBaseT<T>::resampledGenerate(int32_t *output)
while(rsm->process(), rsm->out_count != 0)
{
int16_t in[2];
static_cast<T *>(this)->nativeGenerate(in);
static_cast<T *>(this)->nativeTick(in);
f_in[0] = scale * (float)in[0];
f_in[1] = scale * (float)in[1];
rsm->inp_count = 1;
@ -210,8 +238,8 @@ void OPNChipBaseT<T>::resampledGenerate(int32_t *output)
rsm->out_count = 1;
rsm->out_data = f_out;
}
output[0] = std::lround(f_out[0]);
output[1] = std::lround(f_out[1]);
output[0] = static_cast<int32_t>(std::lround(f_out[0]));
output[1] = static_cast<int32_t>(std::lround(f_out[1]));
}
#else
template <class T>
@ -220,7 +248,7 @@ void OPNChipBaseT<T>::resampledGenerate(int32_t *output)
if(UNLIKELY(m_runningAtPcmRate))
{
int16_t in[2];
static_cast<T *>(this)->nativeGenerate(in);
static_cast<T *>(this)->nativeTick(in);
output[0] = (int32_t)in[0] * T::resamplerPreAmplify / T::resamplerPostAttenuate;
output[1] = (int32_t)in[1] * T::resamplerPreAmplify / T::resamplerPostAttenuate;
return;
@ -233,7 +261,7 @@ void OPNChipBaseT<T>::resampledGenerate(int32_t *output)
m_oldsamples[0] = m_samples[0];
m_oldsamples[1] = m_samples[1];
int16_t buffer[2];
static_cast<T *>(this)->nativeGenerate(buffer);
static_cast<T *>(this)->nativeTick(buffer);
m_samples[0] = buffer[0] * T::resamplerPreAmplify;
m_samples[1] = buffer[1] * T::resamplerPreAmplify;
samplecnt -= rateratio;

View file

@ -0,0 +1,300 @@
/*
* FileAndMemoryReader - a tiny helper to utify file reading from a disk and memory block
*
* Copyright (c) 2015-2018 Vitaly Novichkov <admin@wohlnet.ru>
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the "Software"),
* to deal in the Software without restriction, including without limitation the
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
* sell copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included
* in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
* OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
* DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
* ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
* DEALINGS IN THE SOFTWARE.
*/
#pragma once
#ifndef FILE_AND_MEM_READER_HHHH
#define FILE_AND_MEM_READER_HHHH
#include <string> // std::string
#include <cstdio> // std::fopen, std::fread, std::fseek, std::ftell, std::fclose, std::feof
#include <stdint.h> // uint*_t
#include <stddef.h> // size_t and friends
#ifdef _WIN32
#define NOMINMAX 1
#include <cstring> // std::strlen
#include <windows.h> // MultiByteToWideChar
#endif
/**
* @brief A little class gives able to read filedata from disk and also from a memory segment
*/
class FileAndMemReader
{
//! Currently loaded filename (empty for a memory blocks)
std::string m_file_name;
//! File reader descriptor
std::FILE *m_fp;
//! Memory pointer descriptor
const void *m_mp;
//! Size of memory block
size_t m_mp_size;
//! Cursor position in the memory block
size_t m_mp_tell;
public:
/**
* @brief Relation direction
*/
enum relTo
{
//! At begin position
SET = SEEK_SET,
//! At current position
CUR = SEEK_CUR,
//! At end position
END = SEEK_END
};
/**
* @brief C.O.: It's a constructor!
*/
FileAndMemReader() :
m_fp(NULL),
m_mp(NULL),
m_mp_size(0),
m_mp_tell(0)
{}
/**
* @brief C.O.: It's a destructor!
*/
~FileAndMemReader()
{
close();
}
/**
* @brief Open file from a disk
* @param path Path to the file in UTF-8 (even on Windows!)
*/
void openFile(const char *path)
{
if(m_fp)
this->close();//Close previously opened file first!
#if !defined(_WIN32) || defined(__WATCOMC__)
m_fp = std::fopen(path, "rb");
#else
wchar_t widePath[MAX_PATH];
int size = MultiByteToWideChar(CP_UTF8, 0, path, static_cast<int>(std::strlen(path)), widePath, MAX_PATH);
widePath[size] = '\0';
m_fp = _wfopen(widePath, L"rb");
#endif
m_file_name = path;
m_mp = NULL;
m_mp_size = 0;
m_mp_tell = 0;
}
/**
* @brief Open file from memory block
* @param mem Pointer to the memory block
* @param lenght Size of given block
*/
void openData(const void *mem, size_t lenght)
{
if(m_fp)
this->close();//Close previously opened file first!
m_fp = NULL;
m_mp = mem;
m_mp_size = lenght;
m_mp_tell = 0;
}
/**
* @brief Seek to given position
* @param pos Offset or position
* @param rel_to Relation (at begin, at current, or at end)
*/
void seek(long pos, int rel_to)
{
if(!this->isValid())
return;
if(m_fp)//If a file
{
std::fseek(m_fp, pos, rel_to);
}
else//If a memory block
{
switch(rel_to)
{
case SET:
m_mp_tell = static_cast<size_t>(pos);
break;
case END:
m_mp_tell = m_mp_size - static_cast<size_t>(pos);
break;
case CUR:
m_mp_tell = m_mp_tell + static_cast<size_t>(pos);
break;
}
if(m_mp_tell > m_mp_size)
m_mp_tell = m_mp_size;
}
}
/**
* @brief Seek to given position (unsigned integer 64 as relation. Negative values not supported)
* @param pos Offset or position
* @param rel_to Relation (at begin, at current, or at end)
*/
inline void seeku(uint64_t pos, int rel_to)
{
this->seek(static_cast<long>(pos), rel_to);
}
/**
* @brief Read the buffer from a file
* @param buf Pointer to the destination memory block
* @param num Number of elements
* @param size Size of one element
* @return Size
*/
size_t read(void *buf, size_t num, size_t size)
{
if(!this->isValid())
return 0;
if(m_fp)
return std::fread(buf, num, size, m_fp);
else
{
size_t pos = 0;
size_t maxSize = static_cast<size_t>(size * num);
while((pos < maxSize) && (m_mp_tell < m_mp_size))
{
reinterpret_cast<uint8_t *>(buf)[pos] = reinterpret_cast<const uint8_t *>(m_mp)[m_mp_tell];
m_mp_tell++;
pos++;
}
return pos / num;
}
}
/**
* @brief Get one byte and seek forward
* @return Readed byte or EOF (a.k.a. -1)
*/
int getc()
{
if(!this->isValid())
return -1;
if(m_fp)//If a file
{
return std::getc(m_fp);
}
else //If a memory block
{
if(m_mp_tell >= m_mp_size)
return -1;
int x = reinterpret_cast<const uint8_t *>(m_mp)[m_mp_tell];
m_mp_tell++;
return x;
}
}
/**
* @brief Returns current offset of cursor in a file
* @return Offset position
*/
size_t tell()
{
if(!this->isValid())
return 0;
if(m_fp)//If a file
return static_cast<size_t>(std::ftell(m_fp));
else//If a memory block
return m_mp_tell;
}
/**
* @brief Close the file
*/
void close()
{
if(m_fp)
std::fclose(m_fp);
m_fp = NULL;
m_mp = NULL;
m_mp_size = 0;
m_mp_tell = 0;
}
/**
* @brief Is file instance valid
* @return true if vaild
*/
bool isValid()
{
return (m_fp) || (m_mp);
}
/**
* @brief Is End Of File?
* @return true if end of file was reached
*/
bool eof()
{
if(!this->isValid())
return true;
if(m_fp)
return (std::feof(m_fp) != 0);
else
return m_mp_tell >= m_mp_size;
}
/**
* @brief Get a current file name
* @return File name of currently loaded file
*/
const std::string &fileName()
{
return m_file_name;
}
/**
* @brief Retrieve file size
* @return Size of file in bytes
*/
size_t fileSize()
{
if(!this->isValid())
return 0;
if(!m_fp)
return m_mp_size; //Size of memory block is well known
size_t old_pos = this->tell();
seek(0l, FileAndMemReader::END);
size_t file_size = this->tell();
seek(static_cast<long>(old_pos), FileAndMemReader::SET);
return file_size;
}
};
#endif /* FILE_AND_MEM_READER_HHHH */

View file

@ -1,214 +0,0 @@
#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);
};
#ifdef _MSC_VER
#pragma warning(disable:4146)
#endif
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());
}
#ifdef _MSC_VER
#pragma warning(default:4146)
#endif
template<typename inttype>
inline const fraction<inttype> abs(const fraction<inttype> &f)
{
return fraction<inttype>(abs(f.nom()), abs(f.denom()));
}
#define fraction_blah_func(op) \
template<typename inttype> \
fraction<inttype> operator op \
(const inttype bla, const fraction<inttype> &b) \
{ return fraction<inttype> (bla) op b; }
fraction_blah_func( + )
fraction_blah_func( - )
fraction_blah_func( * )
fraction_blah_func( / )
#undef fraction_blah_func
#define fraction_blah_func(op1, op2) \
template<typename inttype> \
fraction<inttype> &fraction<inttype>::operator op2 (const fraction<inttype> &b) \
{ \
inttype newnom = nom()*b.denom() op1 denom()*b.nom(); \
num2 *= b.denom(); \
num1 = newnom; \
Optim(); \
return *this; \
}
fraction_blah_func( +, += )
fraction_blah_func( -, -= )
#undef fraction_blah_func
template<typename inttype>
fraction<inttype> &fraction<inttype>::operator= (long double orig)
{
if(orig == 0.0)
{
set(0, 0);
return *this;
}
inttype cf[25];
for(int maxdepth=1; maxdepth<25; ++maxdepth)
{
inttype u,v;
long double virhe, a=orig;
int i, viim;
for(i = 0; i < maxdepth; ++i)
{
cf[i] = (inttype)a;
if(cf[i]-1 > cf[i])break;
a = 1.0 / (a - cf[i]);
}
for(viim=i-1; i < maxdepth; ++i)
cf[i] = 0;
u = cf[viim];
v = 1;
for(i = viim-1; i >= 0; --i)
{
inttype w = cf[i] * u + v;
v = u;
u = w;
}
virhe = (orig - (u / (long double)v)) / orig;
set(u, v);
//if(verbose > 4)
// cerr << "Guess: " << *this << " - error = " << virhe*100 << "%\n";
if(virhe < 1e-8 && virhe > -1e-8)break;
}
//if(verbose > 4)
//{
// cerr << "Fraction=" << orig << ": " << *this << endl;
//}
return *this;
}
/*
template<typename inttype>
ostream &operator << (ostream &dest, const fraction<inttype> &m)
{
if(m.denom() == (inttype)1) return dest << m.nom();
return dest << m.nom() << '/' << m.denom();
}
*/
#endif

View file

@ -110,6 +110,7 @@ struct opnInstMeta2
uint16_t ms_sound_kon; // Number of milliseconds it produces sound;
uint16_t ms_sound_koff;
double fine_tune;
int8_t midi_velocity_offset;
#if 0
opnInstMeta2() {}
explicit opnInstMeta2(const opnInstMeta &d);
@ -120,6 +121,16 @@ OPNDATA_BYTE_COMPARABLE(struct opnInstMeta2)
#undef OPNDATA_BYTE_COMPARABLE
#pragma pack(pop)
/**
* @brief Bank global setup
*/
struct OpnBankSetup
{
int volumeModel;
int lfoEnable;
int lfoFrequency;
};
#if 0
/**
* @brief Conversion of storage formats
@ -127,11 +138,21 @@ OPNDATA_BYTE_COMPARABLE(struct opnInstMeta2)
inline opnInstMeta2::opnInstMeta2(const opnInstMeta &d)
: tone(d.tone), flags(d.flags),
ms_sound_kon(d.ms_sound_kon), ms_sound_koff(d.ms_sound_koff),
fine_tune(d.fine_tune)
fine_tune(d.fine_tune), midi_velocity_offset(d.midi_velocity_offset)
{
opn[0] = ::opn[d.opnno1];
opn[1] = ::opn[d.opnno2];
}
#endif
/**
* @brief Convert external instrument to internal instrument
*/
void cvt_OPNI_to_FMIns(opnInstMeta2 &dst, const struct OPN2_Instrument &src);
/**
* @brief Convert internal instrument to external instrument
*/
void cvt_FMIns_to_OPNI(struct OPN2_Instrument &dst, const opnInstMeta2 &src);
#endif // OPNMIDI_OPNBANK_H

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -40,7 +40,7 @@ template <class T>
class BasicBankMap
{
public:
typedef uint16_t key_type; /* the bank identifier */
typedef size_t key_type; /* the bank identifier */
typedef T mapped_type;
typedef std::pair<key_type, T> value_type;

View file

@ -0,0 +1,82 @@
/*
* 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) 2015-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 "opnbank.h"
template <class WOPNI>
static void cvt_generic_to_FMIns(opnInstMeta2 &ins, const WOPNI &in)
{
ins.tone = in.percussion_key_number;
ins.flags = in.inst_flags;
/* Junk, delete later */
ins.fine_tune = 0.0;
/* Junk, delete later */
ins.opn[0].fbalg = in.fbalg;
ins.opn[0].lfosens = in.lfosens;
ins.opn[0].finetune = in.note_offset;
ins.midi_velocity_offset = in.midi_velocity_offset;
for(size_t op = 0; op < 4; op++)
{
ins.opn[0].OPS[op].data[0] = in.operators[op].dtfm_30;
ins.opn[0].OPS[op].data[1] = in.operators[op].level_40;
ins.opn[0].OPS[op].data[2] = in.operators[op].rsatk_50;
ins.opn[0].OPS[op].data[3] = in.operators[op].amdecay1_60;
ins.opn[0].OPS[op].data[4] = in.operators[op].decay2_70;
ins.opn[0].OPS[op].data[5] = in.operators[op].susrel_80;
ins.opn[0].OPS[op].data[6] = in.operators[op].ssgeg_90;
}
ins.opn[1] = ins.opn[0];
ins.ms_sound_kon = in.delay_on_ms;
ins.ms_sound_koff = in.delay_off_ms;
}
template <class WOPNI>
static void cvt_FMIns_to_generic(WOPNI &ins, const opnInstMeta2 &in)
{
ins.percussion_key_number = in.tone;
ins.inst_flags = in.flags;
ins.fbalg = in.opn[0].fbalg;
ins.lfosens = in.opn[0].lfosens;
ins.note_offset = in.opn[0].finetune;
ins.midi_velocity_offset = in.midi_velocity_offset;
for(size_t op = 0; op < 4; op++)
{
ins.operators[op].dtfm_30 = in.opn[0].OPS[op].data[0];
ins.operators[op].level_40 = in.opn[0].OPS[op].data[1];
ins.operators[op].rsatk_50 = in.opn[0].OPS[op].data[2];
ins.operators[op].amdecay1_60 = in.opn[0].OPS[op].data[3];
ins.operators[op].decay2_70 = in.opn[0].OPS[op].data[4];
ins.operators[op].susrel_80 = in.opn[0].OPS[op].data[5];
ins.operators[op].ssgeg_90 = in.opn[0].OPS[op].data[6];
}
ins.delay_on_ms = in.ms_sound_kon;
ins.delay_off_ms = in.ms_sound_koff;
}

View file

@ -22,542 +22,203 @@
*/
#include "opnmidi_private.hpp"
#ifndef OPNMIDI_DISABLE_MIDI_SEQUENCER
# ifndef OPNMIDI_DISABLE_MUS_SUPPORT
# include "opnmidi_mus2mid.h"
# endif
# ifndef OPNMIDI_DISABLE_XMI_SUPPORT
# include "opnmidi_xmi2mid.h"
# endif
#endif //OPNMIDI_DISABLE_MIDI_SEQUENCER
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;
//}
#include "opnmidi_cvt.hpp"
#include "wopn/wopn_file.h"
bool OPNMIDIplay::LoadBank(const std::string &filename)
{
fileReader file;
FileAndMemReader file;
file.openFile(filename.c_str());
return LoadBank(file);
}
bool OPNMIDIplay::LoadBank(const void *data, size_t size)
{
fileReader file;
FileAndMemReader file;
file.openData(data, (size_t)size);
return LoadBank(file);
}
size_t readU16BE(OPNMIDIplay::fileReader &fr, uint16_t &out)
void cvt_OPNI_to_FMIns(opnInstMeta2 &ins, const OPN2_Instrument &in)
{
uint8_t arr[2];
size_t ret = fr.read(arr, 1, 2);
out = arr[1];
out |= ((arr[0] << 8) & 0xFF00);
return ret;
return cvt_generic_to_FMIns(ins, in);
}
size_t readS16BE(OPNMIDIplay::fileReader &fr, int16_t &out)
void cvt_FMIns_to_OPNI(OPN2_Instrument &ins, const opnInstMeta2 &in)
{
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;
cvt_FMIns_to_generic(ins, in);
}
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)
bool OPNMIDIplay::LoadBank(FileAndMemReader &fr)
{
int err = 0;
WOPNFile *wopn = NULL;
char *raw_file_data = NULL;
size_t fsize;
ADL_UNUSED(fsize);
if(!fr.isValid())
{
errorStringOut = "Can't load bank file: Invalid data stream!";
errorStringOut = "Custom bank: 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_percussive_banks = 1;
if(fr.read(magic, 1, 11) != 11)
// Read complete bank file into the memory
fsize = fr.fileSize();
fr.seek(0, FileAndMemReader::SET);
// Allocate necessary memory block
raw_file_data = (char*)malloc(fsize);
if(!raw_file_data)
{
errorStringOut = "Can't load bank file: Can't read magic number!";
errorStringOut = "Custom bank: Out of memory before of read!";
return false;
}
fr.read(raw_file_data, 1, fsize);
bool is1 = std::strncmp(magic, wopn2_magic1, 11) == 0;
bool is2 = std::strncmp(magic, wopn2_magic2, 11) == 0;
// Parse bank file from the memory
wopn = WOPN_LoadBankFromMem((void*)raw_file_data, fsize, &err);
//Free the buffer no more needed
free(raw_file_data);
if(!is1 && !is2)
// Check for any erros
if(!wopn)
{
errorStringOut = "Can't load bank file: Invalid magic number!";
switch(err)
{
case WOPN_ERR_BAD_MAGIC:
errorStringOut = "Custom bank: Invalid magic!";
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!";
case WOPN_ERR_UNEXPECTED_ENDING:
errorStringOut = "Custom bank: Unexpected ending!";
return false;
}
version = toUint16LE(ver);
if(version < 2 || version > latest_version)
{
errorStringOut = "Can't load bank file: unsupported WOPN version!";
case WOPN_ERR_INVALID_BANKS_COUNT:
errorStringOut = "Custom bank: Invalid banks count!";
return false;
case WOPN_ERR_NEWER_VERSION:
errorStringOut = "Custom bank: Version is newer than supported by this library!";
return false;
case WOPN_ERR_OUT_OF_MEMORY:
errorStringOut = "Custom bank: Out of memory!";
return false;
default:
errorStringOut = "Custom bank: Unknown error!";
return false;
}
}
opn.cleanInstrumentBanks();
if((readU16BE(fr, count_melodic_banks) != 2) || (readU16BE(fr, count_percussive_banks) != 2))
{
errorStringOut = "Can't load bank file: Can't read count of banks!";
return false;
}
m_synth.m_insBankSetup.volumeModel = wopn->volume_model;
m_synth.m_insBankSetup.lfoEnable = (wopn->lfo_freq & 8) != 0;
m_synth.m_insBankSetup.lfoFrequency = wopn->lfo_freq & 7;
m_setup.VolumeModel = OPNMIDI_VolumeModel_AUTO;
m_setup.lfoEnable = -1;
m_setup.lfoFrequency = -1;
if((count_melodic_banks < 1) || (count_percussive_banks < 1))
{
errorStringOut = "Custom bank: Too few banks in this file!";
return false;
}
m_synth.m_insBanks.clear();
if(fr.read(&opn.regLFO, 1, 1) != 1)
{
errorStringOut = "Can't load bank file: Can't read LFO registry state!";
return false;
}
uint16_t slots_counts[2] = {wopn->banks_count_melodic, wopn->banks_count_percussion};
WOPNBank *slots_src_ins[2] = { wopn->banks_melodic, wopn->banks_percussive };
opn.cleanInstrumentBanks();
std::vector<OPN2::Bank *> banks;
banks.reserve(count_melodic_banks + count_percussive_banks);
if(version >= 2)//Read bank meta-entries
for(size_t ss = 0; ss < 2; ss++)
{
for(uint16_t i = 0; i < count_melodic_banks; i++)
for(size_t i = 0; i < slots_counts[ss]; i++)
{
uint8_t bank_meta[34];
if(fr.read(bank_meta, 1, 34) != 34)
size_t bankno = (slots_src_ins[ss][i].bank_midi_msb * 256) +
(slots_src_ins[ss][i].bank_midi_lsb) +
(ss ? size_t(OPN2::PercussionTag) : 0);
OPN2::Bank &bank = m_synth.m_insBanks[bankno];
for(int j = 0; j < 128; j++)
{
opn.cleanInstrumentBanks();
errorStringOut = "Custom bank: Fail to read melodic bank meta-data!";
return false;
}
uint16_t bankno = uint16_t(bank_meta[33]) * 256 + uint16_t(bank_meta[32]);
OPN2::Bank &bank = opn.dynamic_banks[bankno];
//strncpy(bank.name, char_p(bank_meta), 32);
banks.push_back(&bank);
}
for(uint16_t i = 0; i < count_percussive_banks; i++)
{
uint8_t bank_meta[34];
if(fr.read(bank_meta, 1, 34) != 34)
{
opn.cleanInstrumentBanks();
errorStringOut = "Custom bank: Fail to read percussion bank meta-data!";
return false;
}
uint16_t bankno = uint16_t(bank_meta[33]) * 256 + uint16_t(bank_meta[32]) + OPN2::PercussionTag;
OPN2::Bank &bank = opn.dynamic_banks[bankno];
//strncpy(bank.name, char_p(bank_meta), 32);
banks.push_back(&bank);
opnInstMeta2 &ins = bank.ins[j];
std::memset(&ins, 0, sizeof(opnInstMeta2));
WOPNInstrument &inIns = slots_src_ins[ss][i].ins[j];
cvt_generic_to_FMIns(ins, inIns);
}
}
size_t total = 128 * opn.dynamic_banks.size();
for(size_t i = 0; i < total; i++)
{
opnInstMeta2 &meta = banks[i / 128]->ins[i % 128];
opnInstData &data = meta.opn[0];
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.cleanInstrumentBanks();
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);
}
meta.flags = 0;
if(version >= 2)
{
meta.ms_sound_kon = toUint16BE(idata + 65);
meta.ms_sound_koff = toUint16BE(idata + 67);
if((meta.ms_sound_kon == 0) && (meta.ms_sound_koff == 0))
meta.flags |= opnInstMeta::Flag_NoSound;
}
else
{
meta.ms_sound_kon = 1000;
meta.ms_sound_koff = 500;
}
meta.opn[1] = meta.opn[0];
/* Junk, delete later */
meta.fine_tune = 0.0;
/* Junk, delete later */
}
applySetup();
WOPN_Free(wopn);
return true;
}
#ifndef OPNMIDI_DISABLE_MIDI_SEQUENCER
bool OPNMIDIplay::LoadMIDI_pre()
{
if(m_synth.m_insBanks.empty())
{
errorStringOut = "Bank is not set! Please load any instruments bank by using of adl_openBankFile() or adl_openBankData() functions!";
return false;
}
/**** Set all properties BEFORE starting of actial file reading! ****/
resetMIDI();
applySetup();
return true;
}
bool OPNMIDIplay::LoadMIDI_post()
{
MidiSequencer::FileFormat format = m_sequencer.getFormat();
if(format == MidiSequencer::Format_CMF)
{
errorStringOut = "OPNMIDI doesn't supports CMF, use ADLMIDI to play this file!";
/* As joke, why not to try implemented the converter of patches from OPL3 into OPN2? */
return false;
}
else if(format == MidiSequencer::Format_RSXX)
{
m_synth.m_musicMode = OPN2::MODE_RSXX;
m_synth.m_volumeScale = OPN2::VOLUME_Generic;
m_synth.m_numChips = 2;
}
else if(format == MidiSequencer::Format_IMF)
{
errorStringOut = "OPNMIDI doesn't supports IMF, use ADLMIDI to play this file!";
/* Same as for CMF */
return false;
}
m_setup.tick_skip_samples_delay = 0;
m_synth.reset(m_setup.emulator, m_setup.PCM_RATE, this); // Reset OPN2 chip
m_chipChannels.clear();
m_chipChannels.resize(m_synth.m_numChannels);
return true;
}
bool OPNMIDIplay::LoadMIDI(const std::string &filename)
{
fileReader file;
FileAndMemReader file;
file.openFile(filename.c_str());
if(!LoadMIDI(file))
if(!LoadMIDI_pre())
return false;
if(!m_sequencer.loadMIDI(file))
{
errorStringOut = m_sequencer.getErrorString();
return false;
}
if(!LoadMIDI_post())
return false;
return true;
}
bool OPNMIDIplay::LoadMIDI(const void *data, size_t size)
{
fileReader file;
FileAndMemReader 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_banks.empty())
if(!LoadMIDI_pre())
return false;
if(!m_sequencer.loadMIDI(file))
{
errorStringOut = "Bank is not set! Please load any instruments bank by using of adl_openBankFile() or adl_openBankData() functions!";
errorStringOut = m_sequencer.getErrorString();
return false;
}
if(!fr.isValid())
{
errorStringOut = "Invalid data stream!\n";
#ifndef _WIN32
errorStringOut += std::strerror(errno);
#endif
if(!LoadMIDI_post())
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;
}
#ifndef OPNMIDI_DISABLE_MUS_SUPPORT
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;
}
#endif //OPNMIDI_DISABLE_MUS_SUPPORT
#ifndef OPNMIDI_DISABLE_XMI_SUPPORT
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;
}
#endif //OPNMIDI_DISABLE_XMI_SUPPORT
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;
//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>());
InvDeltaTicks = fraction<uint64_t>(1, 1000000l * static_cast<uint64_t>(DeltaTicks));
Tempo = fraction<uint64_t>(1, static_cast<uint64_t>(DeltaTicks) * 2);
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
if(!buildTrackData())
{
errorStringOut = fr._fileName + ": MIDI data parsing error has occouped!\n" + errorString;
return false;
}
opn.Reset(m_setup.emulator, m_setup.PCM_RATE); // Reset OPN2 chip
ch.clear();
ch.resize(opn.NumChannels);
return true;
}
#endif //OPNMIDI_DISABLE_MIDI_SEQUENCER

File diff suppressed because it is too large Load diff

View file

@ -48,23 +48,60 @@
#include "chips/gx_opn2.h"
#endif
static const unsigned opn2_emulatorSupport = 0
#ifndef OPNMIDI_DISABLE_NUKED_EMULATOR
| (1u << OPNMIDI_EMU_NUKED)
#endif
#ifndef OPNMIDI_DISABLE_MAME_EMULATOR
| (1u << OPNMIDI_EMU_MAME)
#endif
#ifndef OPNMIDI_DISABLE_GENS_EMULATOR
| (1u << OPNMIDI_EMU_GENS)
#endif
#ifndef OPNMIDI_DISABLE_GX_EMULATOR
| (1u << OPNMIDI_EMU_GX)
#endif
;
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)
//! Check emulator availability
bool opn2_isEmulatorAvailable(int emulator)
{
out_card = in_channel / 6;
uint8_t ch4 = in_channel % 6;
out_port = ((ch4 < 3) ? 0 : 1);
out_ch = ch4 % 3;
return (opn2_emulatorSupport & (1u << (unsigned)emulator)) != 0;
}
void OPN2::cleanInstrumentBanks()
//! Find highest emulator
int opn2_getHighestEmulator()
{
dynamic_banks.clear();
int emu = -1;
for(unsigned m = opn2_emulatorSupport; m > 0; m >>= 1)
++emu;
return emu;
}
//! Find lowest emulator
int opn2_getLowestEmulator()
{
int emu = -1;
unsigned m = opn2_emulatorSupport;
if(m > 0)
{
for(emu = 0; (m & 1) == 0; m >>= 1)
++emu;
}
return emu;
}
static const uint32_t g_noteChannelsMap[6] = { 0, 1, 2, 4, 5, 6 };
static inline void getOpnChannel(size_t in_channel,
size_t &out_chip,
uint8_t &out_port,
uint32_t &out_ch)
{
out_chip = in_channel / 6;
size_t ch4 = in_channel % 6;
out_port = ((ch4 < 3) ? 0 : 1);
out_ch = static_cast<uint32_t>(ch4 % 3);
}
static opnInstMeta2 makeEmptyInstrument()
@ -75,71 +112,128 @@ static opnInstMeta2 makeEmptyInstrument()
return ins;
}
const opnInstMeta2 OPN2::emptyInstrument = makeEmptyInstrument();
const opnInstMeta2 OPN2::m_emptyInstrument = makeEmptyInstrument();
OPN2::OPN2() :
regLFO(0),
NumCards(1),
m_regLFOSetup(0),
m_numChips(1),
m_scaleModulators(false),
m_runAtPcmRate(false),
m_softPanning(false),
m_musicMode(MODE_MIDI),
m_volumeScale(VOLUME_Generic)
m_volumeScale(VOLUME_Generic),
m_lfoEnable(false),
m_lfoFrequency(0)
{
m_insBankSetup.volumeModel = OPN2::VOLUME_Generic;
m_insBankSetup.lfoEnable = false;
m_insBankSetup.lfoFrequency = 0;
// Initialize blank instruments banks
cleanInstrumentBanks();
m_insBanks.clear();
}
OPN2::~OPN2()
{
ClearChips();
clearChips();
}
void OPN2::PokeO(size_t card, uint8_t port, uint8_t index, uint8_t value)
bool OPN2::setupLocked()
{
cardsOP2[card]->writeReg(port, index, value);
return (m_musicMode == MODE_CMF ||
m_musicMode == MODE_IMF ||
m_musicMode == MODE_RSXX);
}
void OPN2::NoteOff(size_t c)
void OPN2::writeReg(size_t chip, uint8_t port, uint8_t index, uint8_t value)
{
unsigned card;
uint8_t port, cc;
uint8_t ch4 = c % 6;
getOpnChannel(uint16_t(c), card, port, cc);
PokeO(card, 0, 0x28, NoteChannels[ch4]);
m_chips[chip]->writeReg(port, index, value);
}
void OPN2::NoteOn(unsigned c, double hertz) // Hertz range: 0..131071
void OPN2::writeRegI(size_t chip, uint8_t port, uint32_t index, uint32_t value)
{
unsigned card;
uint8_t port, cc;
uint8_t ch4 = c % 6;
getOpnChannel(uint16_t(c), card, port, cc);
m_chips[chip]->writeReg(port, static_cast<uint8_t>(index), static_cast<uint8_t>(value));
}
uint16_t x2 = 0x0000;
void OPN2::writePan(size_t chip, uint32_t index, uint32_t value)
{
m_chips[chip]->writePan(static_cast<uint16_t>(index), static_cast<uint8_t>(value));
}
if(hertz < 0 || hertz > 262143) // Avoid infinite loop
void OPN2::noteOff(size_t c)
{
size_t chip;
uint8_t port;
uint32_t cc;
size_t ch4 = c % 6;
getOpnChannel(c, chip, port, cc);
writeRegI(chip, 0, 0x28, g_noteChannelsMap[ch4]);
}
void OPN2::noteOn(size_t c, double hertz) // Hertz range: 0..131071
{
size_t chip;
uint8_t port;
uint32_t cc;
size_t ch4 = c % 6;
getOpnChannel(c, chip, port, cc);
if(hertz < 0) // Avoid infinite loop
return;
while((hertz >= 1023.75) && (x2 < 0x3800))
uint32_t octave = 0, ftone = 0, mul_offset = 0;
const opnInstData &adli = m_insCache[c];
//Basic range until max of octaves reaching
while((hertz >= 1023.75) && (octave < 0x3800))
{
hertz /= 2.0; // Calculate octave
x2 += 0x800;
octave += 0x800;
}
//Extended range, rely on frequency multiplication increment
while(hertz >= 2036.75)
{
hertz /= 2.0; // Calculate octave
mul_offset++;
}
ftone = octave + static_cast<uint32_t>(hertz + 0.5);
for(size_t op = 0; op < 4; op++)
{
uint32_t reg = adli.OPS[op].data[0];
uint16_t address = static_cast<uint16_t>(0x30 + (op * 4) + cc);
if(mul_offset > 0) // Increase frequency multiplication value
{
uint32_t dt = reg & 0xF0;
uint32_t mul = reg & 0x0F;
if((mul + mul_offset) > 0x0F)
{
mul_offset = 0;
mul = 0x0F;
}
writeRegI(chip, port, address, uint8_t(dt | (mul + mul_offset)));
}
else
{
writeRegI(chip, port, address, uint8_t(reg));
}
}
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);
writeRegI(chip, port, 0xA4 + cc, (ftone>>8) & 0xFF);//Set frequency and octave
writeRegI(chip, port, 0xA0 + cc, ftone & 0xFF);
writeRegI(chip, 0, 0x28, 0xF0 + g_noteChannelsMap[ch4]);
}
void OPN2::Touch_Real(unsigned c, unsigned volume, uint8_t brightness)
void OPN2::touchNote(size_t c, uint8_t volume, uint8_t brightness)
{
if(volume > 127) volume = 127;
unsigned card;
uint8_t port, cc;
getOpnChannel(c, card, port, cc);
size_t chip;
uint8_t port;
uint32_t cc;
getOpnChannel(c, chip, port, cc);
const opnInstData &adli = ins[c];
const opnInstData &adli = m_insCache[c];
uint8_t op_vol[4] =
{
@ -170,16 +264,16 @@ void OPN2::Touch_Real(unsigned c, unsigned volume, uint8_t brightness)
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;
bool do_op = alg_do[alg][op] || m_scaleModulators;
uint32_t x = op_vol[op];
uint32_t vol_res = do_op ? (127 - (static_cast<uint32_t>(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))));
brightness = static_cast<uint32_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);
vol_res = (127 - (brightness * (127 - (static_cast<uint32_t>(vol_res) & 127))) / 127);
}
PokeO(card, port, 0x40 + cc + (4 * op), vol_res);
writeRegI(chip, port, 0x40 + cc + (4 * op), vol_res);
}
// Correct formula (ST3, AdPlug):
// 63-((63-(instrvol))/63)*chanvol
@ -189,57 +283,68 @@ void OPN2::Touch_Real(unsigned c, unsigned volume, uint8_t brightness)
// 63 + chanvol * (instrvol / 63.0 - 1)
}
void OPN2::Patch(uint16_t c, const opnInstData &adli)
void OPN2::setPatch(size_t c, const opnInstData &instrument)
{
unsigned card;
uint8_t port, cc;
getOpnChannel(uint16_t(c), card, port, cc);
ins[c] = adli;
#if 1 //Reg1-Op1, Reg1-Op2, Reg1-Op3, Reg1-Op4,....
size_t chip;
uint8_t port;
uint32_t cc;
getOpnChannel(c, chip, port, cc);
m_insCache[c] = instrument;
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]);
writeRegI(chip, port, 0x30 + (0x10 * d) + (op * 4) + cc, instrument.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
writeRegI(chip, port, 0xB0 + cc, instrument.fbalg);//Feedback/Algorithm
m_regLFOSens[c] = (m_regLFOSens[c] & 0xC0) | (instrument.lfosens & 0x3F);
writeRegI(chip, port, 0xB4 + cc, m_regLFOSens[c]);//Panorame and LFO bits
}
void OPN2::Pan(unsigned c, unsigned value)
void OPN2::setPan(size_t c, uint8_t value)
{
unsigned card;
uint8_t port, cc;
getOpnChannel(uint16_t(c), card, port, cc);
const opnInstData &adli = ins[c];
uint8_t val = (value & 0xC0) | (adli.lfosens & 0x3F);
regBD[c] = val;
PokeO(card, port, 0xB4 + cc, val);
size_t chip;
uint8_t port;
uint32_t cc;
getOpnChannel(c, chip, port, cc);
const opnInstData &adli = m_insCache[c];
uint8_t val = 0;
if(m_softPanning)
{
val = (OPN_PANNING_BOTH & 0xC0) | (adli.lfosens & 0x3F);
writePan(chip, c % 6, value);
writeRegI(chip, port, 0xB4 + cc, val);
}
else
{
int panning = 0;
if(value < 64 + 32) panning |= OPN_PANNING_LEFT;
if(value >= 64 - 32) panning |= OPN_PANNING_RIGHT;
val = (panning & 0xC0) | (adli.lfosens & 0x3F);
writePan(chip, c % 6, 64);
writeRegI(chip, port, 0xB4 + cc, val);
}
m_regLFOSens[c] = val;
}
void OPN2::Silence() // Silence all OPL channels.
void OPN2::silenceAll() // Silence all OPL channels.
{
for(unsigned c = 0; c < NumChannels; ++c)
for(size_t c = 0; c < m_numChannels; ++c)
{
NoteOff(c);
Touch_Real(c, 0);
noteOff(c);
touchNote(c, 0);
}
}
void OPN2::ChangeVolumeRangesModel(OPNMIDI_VolumeModels volumeModel)
void OPN2::commitLFOSetup()
{
uint8_t regLFOSetup = (m_lfoEnable ? 8 : 0) | (m_lfoFrequency & 7);
m_regLFOSetup = regLFOSetup;
for(size_t chip = 0; chip < m_numChips; ++chip)
writeReg(chip, 0, 0x22, regLFOSetup);
}
void OPN2::setVolumeScaleModel(OPNMIDI_VolumeModels volumeModel)
{
switch(volumeModel)
{
@ -250,8 +355,8 @@ void OPN2::ChangeVolumeRangesModel(OPNMIDI_VolumeModels volumeModel)
m_volumeScale = OPN2::VOLUME_Generic;
break;
case OPNMIDI_VolumeModel_CMF:
m_volumeScale = OPN2::VOLUME_CMF;
case OPNMIDI_VolumeModel_NativeOPN2:
m_volumeScale = OPN2::VOLUME_NATIVE;
break;
case OPNMIDI_VolumeModel_DMX:
@ -268,70 +373,101 @@ void OPN2::ChangeVolumeRangesModel(OPNMIDI_VolumeModels volumeModel)
}
}
void OPN2::ClearChips()
OPNMIDI_VolumeModels OPN2::getVolumeScaleModel()
{
for(size_t i = 0; i < cardsOP2.size(); i++)
cardsOP2[i].reset(NULL);
cardsOP2.clear();
switch(m_volumeScale)
{
default:
case OPN2::VOLUME_Generic:
return OPNMIDI_VolumeModel_Generic;
case OPN2::VOLUME_NATIVE:
return OPNMIDI_VolumeModel_NativeOPN2;
case OPN2::VOLUME_DMX:
return OPNMIDI_VolumeModel_DMX;
case OPN2::VOLUME_APOGEE:
return OPNMIDI_VolumeModel_APOGEE;
case OPN2::VOLUME_9X:
return OPNMIDI_VolumeModel_9X;
}
}
void OPN2::Reset(int emulator, unsigned long PCM_RATE)
void OPN2::clearChips()
{
ClearChips();
ins.clear();
pit.clear();
regBD.clear();
cardsOP2.resize(NumCards, AdlMIDI_SPtr<OPNChipBase>());
for(size_t i = 0; i < m_chips.size(); i++)
m_chips[i].reset(NULL);
m_chips.clear();
}
for(size_t i = 0; i < cardsOP2.size(); i++)
void OPN2::reset(int emulator, unsigned long PCM_RATE, void *audioTickHandler)
{
#if !defined(ADLMIDI_AUDIO_TICK_HANDLER)
ADL_UNUSED(audioTickHandler);
#endif
clearChips();
m_insCache.clear();
m_regLFOSens.clear();
m_chips.resize(m_numChips, AdlMIDI_SPtr<OPNChipBase>());
for(size_t i = 0; i < m_chips.size(); i++)
{
OPNChipBase *chip;
switch(emulator)
{
default:
assert(false);
abort();
#ifndef OPNMIDI_DISABLE_MAME_EMULATOR
case OPNMIDI_EMU_MAME:
cardsOP2[i].reset(new MameOPN2());
chip = new MameOPN2;
break;
#endif
#ifndef OPNMIDI_DISABLE_NUKED_EMULATOR
case OPNMIDI_EMU_NUKED:
cardsOP2[i].reset(new NukedOPN2());
chip = new NukedOPN2;
break;
#endif
#ifndef OPNMIDI_DISABLE_GENS_EMULATOR
case OPNMIDI_EMU_GENS:
cardsOP2[i].reset(new GensOPN2());
chip = new GensOPN2;
break;
#endif
#ifndef OPNMIDI_DISABLE_GX_EMULATOR
case OPNMIDI_EMU_GX:
cardsOP2[i].reset(new GXOPN2());
chip = new GXOPN2;
break;
#endif
}
cardsOP2[i]->setRate((uint32_t)PCM_RATE, 7670454);
if(runAtPcmRate)
cardsOP2[i]->setRunningAtPcmRate(true);
m_chips[i].reset(chip);
chip->setChipId((uint32_t)i);
chip->setRate((uint32_t)PCM_RATE, 7670454);
if(m_runAtPcmRate)
chip->setRunningAtPcmRate(true);
#if defined(ADLMIDI_AUDIO_TICK_HANDLER)
chip->setAudioTickHandlerInstance(audioTickHandler);
#endif
}
NumChannels = NumCards * 6;
ins.resize(NumChannels, emptyInstrument.opn[0]);
pit.resize(NumChannels, 0);
regBD.resize(NumChannels, 0);
m_numChannels = m_numChips * 6;
m_insCache.resize(m_numChannels, m_emptyInstrument.opn[0]);
m_regLFOSens.resize(m_numChannels, 0);
for(unsigned card = 0; card < NumCards; ++card)
uint8_t regLFOSetup = (m_lfoEnable ? 8 : 0) | (m_lfoFrequency & 7);
m_regLFOSetup = regLFOSetup;
for(size_t card = 0; card < m_numChips; ++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
writeReg(card, 0, 0x22, regLFOSetup);//push current LFO state
writeReg(card, 0, 0x27, 0x00); //set Channel 3 normal mode
writeReg(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
writeReg(card, 0, 0x28, 0x00 ); //Note Off 0 channel
writeReg(card, 0, 0x28, 0x01 ); //Note Off 1 channel
writeReg(card, 0, 0x28, 0x02 ); //Note Off 2 channel
writeReg(card, 0, 0x28, 0x04 ); //Note Off 3 channel
writeReg(card, 0, 0x28, 0x05 ); //Note Off 4 channel
writeReg(card, 0, 0x28, 0x06 ); //Note Off 5 channel
}
Silence();
silenceAll();
}

View file

@ -25,31 +25,12 @@
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];
// }
// Generator callback on audio rate ticks
// 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;
#if defined(ADLMIDI_AUDIO_TICK_HANDLER)
void opn2_audioTickHandler(void *instance, uint32_t chipId, uint32_t rate)
{
reinterpret_cast<OPNMIDIplay *>(instance)->AudioTick(chipId, rate);
}
#endif

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,622 @@
/*
* Wohlstand's OPN2 Bank File - a bank format to store OPN2 timbre data and setup
*
* Copyright (c) 2018 Vitaly Novichkov <admin@wohlnet.ru>
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the "Software"),
* to deal in the Software without restriction, including without limitation the
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
* sell copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included
* in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
* OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
* DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
* ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
* DEALINGS IN THE SOFTWARE.
*/
#include "wopn_file.h"
#include <string.h>
#include <stdlib.h>
static const char *wopn2_magic1 = "WOPN2-BANK\0";
static const char *wopn2_magic2 = "WOPN2-B2NK\0";
static const char *opni_magic1 = "WOPN2-INST\0";
static const char *opni_magic2 = "WOPN2-IN2T\0";
static const uint16_t wopn_latest_version = 2;
enum
{
WOPN_INST_SIZE_V1 = 65,
WOPN_INST_SIZE_V2 = 69
};
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 int16_t toSint16BE(const uint8_t *arr)
{
int16_t num = *(const int8_t *)(&arr[0]);
num *= 1 << 8;
num |= arr[1];
return num;
}
static void fromUint16LE(uint16_t in, uint8_t *arr)
{
arr[0] = in & 0x00FF;
arr[1] = (in >> 8) & 0x00FF;
}
static void fromUint16BE(uint16_t in, uint8_t *arr)
{
arr[1] = in & 0x00FF;
arr[0] = (in >> 8) & 0x00FF;
}
static void fromSint16BE(int16_t in, uint8_t *arr)
{
arr[1] = in & 0x00FF;
arr[0] = ((uint16_t)in >> 8) & 0x00FF;
}
WOPNFile *WOPN_Init(uint16_t melodic_banks, uint16_t percussive_banks)
{
WOPNFile *file = NULL;
if(melodic_banks == 0)
return NULL;
if(percussive_banks == 0)
return NULL;
file = (WOPNFile*)calloc(1, sizeof(WOPNFile));
if(!file)
return NULL;
file->banks_count_melodic = melodic_banks;
file->banks_count_percussion = percussive_banks;
file->banks_melodic = (WOPNBank*)calloc(1, sizeof(WOPNBank) * melodic_banks );
file->banks_percussive = (WOPNBank*)calloc(1, sizeof(WOPNBank) * percussive_banks );
return file;
}
void WOPN_Free(WOPNFile *file)
{
if(file)
{
if(file->banks_melodic)
free(file->banks_melodic);
if(file->banks_percussive)
free(file->banks_percussive);
free(file);
}
}
int WOPN_BanksCmp(const WOPNFile *bank1, const WOPNFile *bank2)
{
int res = 1;
res &= (bank1->version == bank2->version);
res &= (bank1->lfo_freq == bank2->lfo_freq);
res &= (bank1->volume_model == bank2->volume_model);
res &= (bank1->banks_count_melodic == bank2->banks_count_melodic);
res &= (bank1->banks_count_percussion == bank2->banks_count_percussion);
if(res)
{
int i;
for(i = 0; i < bank1->banks_count_melodic; i++)
res &= (memcmp(&bank1->banks_melodic[i], &bank2->banks_melodic[i], sizeof(WOPNBank)) == 0);
if(res)
{
for(i = 0; i < bank1->banks_count_percussion; i++)
res &= (memcmp(&bank1->banks_percussive[i], &bank2->banks_percussive[i], sizeof(WOPNBank)) == 0);
}
}
return res;
}
static void WOPN_parseInstrument(WOPNInstrument *ins, uint8_t *cursor, uint16_t version, uint8_t has_sounding_delays)
{
int l;
strncpy(ins->inst_name, (const char*)cursor, 32);
ins->inst_name[32] = '\0';
ins->note_offset = toSint16BE(cursor + 32);
ins->midi_velocity_offset = 0; /* TODO: for future version > 2 */
ins->percussion_key_number = cursor[34];
ins->inst_flags = 0; /* TODO: for future version > 2 */
ins->fbalg = cursor[35];
ins->lfosens = cursor[36];
for(l = 0; l < 4; l++)
{
size_t off = 37 + (size_t)(l) * 7;
ins->operators[l].dtfm_30 = cursor[off + 0];
ins->operators[l].level_40 = cursor[off + 1];
ins->operators[l].rsatk_50 = cursor[off + 2];
ins->operators[l].amdecay1_60 = cursor[off + 3];
ins->operators[l].decay2_70 = cursor[off + 4];
ins->operators[l].susrel_80 = cursor[off + 5];
ins->operators[l].ssgeg_90 = cursor[off + 6];
}
if((version >= 2) && has_sounding_delays)
{
ins->delay_on_ms = toUint16BE(cursor + 65);
ins->delay_off_ms = toUint16BE(cursor + 67);
/* Null delays indicate the blank instrument in version 2 */
if((version < 3) && ins->delay_on_ms == 0 && ins->delay_off_ms == 0)
ins->inst_flags |= WOPN_Ins_IsBlank;
}
}
static void WOPN_writeInstrument(WOPNInstrument *ins, uint8_t *cursor, uint16_t version, uint8_t has_sounding_delays)
{
int l;
strncpy((char*)cursor, ins->inst_name, 32);
fromSint16BE(ins->note_offset, cursor + 32);
cursor[34] = ins->percussion_key_number;
cursor[35] = ins->fbalg;
cursor[36] = ins->lfosens;
for(l = 0; l < 4; l++)
{
size_t off = 37 + (size_t)(l) * 7;
cursor[off + 0] = ins->operators[l].dtfm_30;
cursor[off + 1] = ins->operators[l].level_40;
cursor[off + 2] = ins->operators[l].rsatk_50;
cursor[off + 3] = ins->operators[l].amdecay1_60;
cursor[off + 4] = ins->operators[l].decay2_70;
cursor[off + 5] = ins->operators[l].susrel_80;
cursor[off + 6] = ins->operators[l].ssgeg_90;
}
if((version >= 2) && has_sounding_delays)
{
if((version < 3) && (ins->inst_flags & WOPN_Ins_IsBlank) != 0)
{
/* Null delays indicate the blank instrument in version 2 */
fromUint16BE(0, cursor + 65);
fromUint16BE(0, cursor + 67);
}
else
{
fromUint16BE(ins->delay_on_ms, cursor + 65);
fromUint16BE(ins->delay_off_ms, cursor + 67);
}
}
}
WOPNFile *WOPN_LoadBankFromMem(void *mem, size_t length, int *error)
{
WOPNFile *outFile = NULL;
uint16_t i = 0, j = 0, k = 0;
uint16_t version = 0;
uint16_t count_melodic_banks = 1;
uint16_t count_percussive_banks = 1;
uint8_t *cursor = (uint8_t *)mem;
WOPNBank *bankslots[2];
uint16_t bankslots_sizes[2];
#define SET_ERROR(err) \
{\
WOPN_Free(outFile);\
if(error)\
{\
*error = err;\
}\
}
#define GO_FORWARD(bytes) { cursor += bytes; length -= bytes; }
if(!cursor)
{
SET_ERROR(WOPN_ERR_NULL_POINTER);
return NULL;
}
{/* Magic number */
if(length < 11)
{
SET_ERROR(WOPN_ERR_UNEXPECTED_ENDING);
return NULL;
}
if(memcmp(cursor, wopn2_magic1, 11) == 0)
{
version = 1;
}
else if(memcmp(cursor, wopn2_magic2, 11) != 0)
{
SET_ERROR(WOPN_ERR_BAD_MAGIC);
return NULL;
}
GO_FORWARD(11);
}
if (version == 0)
{/* Version code */
if(length < 2)
{
SET_ERROR(WOPN_ERR_UNEXPECTED_ENDING);
return NULL;
}
version = toUint16LE(cursor);
if(version > wopn_latest_version)
{
SET_ERROR(WOPN_ERR_NEWER_VERSION);
return NULL;
}
GO_FORWARD(2);
}
{/* Header of WOPN */
uint8_t head[5];
if(length < 5)
{
SET_ERROR(WOPN_ERR_UNEXPECTED_ENDING);
return NULL;
}
memcpy(head, cursor, 5);
count_melodic_banks = toUint16BE(head);
count_percussive_banks = toUint16BE(head + 2);
GO_FORWARD(5);
outFile = WOPN_Init(count_melodic_banks, count_percussive_banks);
if(!outFile)
{
SET_ERROR(WOPN_ERR_OUT_OF_MEMORY);
return NULL;
}
outFile->version = version;
outFile->lfo_freq = head[4];
outFile->volume_model = 0;
}
bankslots_sizes[0] = count_melodic_banks;
bankslots[0] = outFile->banks_melodic;
bankslots_sizes[1] = count_percussive_banks;
bankslots[1] = outFile->banks_percussive;
if(version >= 2) /* Bank names and LSB/MSB titles */
{
for(i = 0; i < 2; i++)
{
for(j = 0; j < bankslots_sizes[i]; j++)
{
if(length < 34)
{
SET_ERROR(WOPN_ERR_UNEXPECTED_ENDING);
return NULL;
}
strncpy(bankslots[i][j].bank_name, (const char*)cursor, 32);
bankslots[i][j].bank_name[32] = '\0';
bankslots[i][j].bank_midi_lsb = cursor[32];
bankslots[i][j].bank_midi_msb = cursor[33];
GO_FORWARD(34);
}
}
}
{/* Read instruments data */
uint16_t insSize = 0;
if(version > 1)
insSize = WOPN_INST_SIZE_V2;
else
insSize = WOPN_INST_SIZE_V1;
for(i = 0; i < 2; i++)
{
if(length < (insSize * 128) * (size_t)bankslots_sizes[i])
{
SET_ERROR(WOPN_ERR_UNEXPECTED_ENDING);
return NULL;
}
for(j = 0; j < bankslots_sizes[i]; j++)
{
for(k = 0; k < 128; k++)
{
WOPNInstrument *ins = &bankslots[i][j].ins[k];
WOPN_parseInstrument(ins, cursor, version, 1);
GO_FORWARD(insSize);
}
}
}
}
#undef GO_FORWARD
#undef SET_ERROR
return outFile;
}
int WOPN_LoadInstFromMem(OPNIFile *file, void *mem, size_t length)
{
uint16_t version = 0;
uint8_t *cursor = (uint8_t *)mem;
uint16_t ins_size;
if(!cursor)
return WOPN_ERR_NULL_POINTER;
#define GO_FORWARD(bytes) { cursor += bytes; length -= bytes; }
{/* Magic number */
if(length < 11)
return WOPN_ERR_UNEXPECTED_ENDING;
if(memcmp(cursor, opni_magic1, 11) == 0)
version = 1;
else if(memcmp(cursor, opni_magic2, 11) != 0)
return WOPN_ERR_BAD_MAGIC;
GO_FORWARD(11);
}
if (version == 0) {/* Version code */
if(length < 2)
return WOPN_ERR_UNEXPECTED_ENDING;
version = toUint16LE(cursor);
if(version > wopn_latest_version)
return WOPN_ERR_NEWER_VERSION;
GO_FORWARD(2);
}
file->version = version;
{/* is drum flag */
if(length < 1)
return WOPN_ERR_UNEXPECTED_ENDING;
file->is_drum = *cursor;
GO_FORWARD(1);
}
if(version > 1)
/* Skip sounding delays are not part of single-instrument file
* two sizes of uint16_t will be subtracted */
ins_size = WOPN_INST_SIZE_V2 - (sizeof(uint16_t) * 2);
else
ins_size = WOPN_INST_SIZE_V1;
if(length < ins_size)
return WOPN_ERR_UNEXPECTED_ENDING;
WOPN_parseInstrument(&file->inst, cursor, version, 0);
GO_FORWARD(ins_size);
return WOPN_ERR_OK;
#undef GO_FORWARD
}
size_t WOPN_CalculateBankFileSize(WOPNFile *file, uint16_t version)
{
size_t final_size = 0;
size_t ins_size = 0;
if(version == 0)
version = wopn_latest_version;
if(!file)
return 0;
final_size += 11 + 2 + 2 + 2 + 1;
/*
* Magic number,
* Version,
* Count of melodic banks,
* Count of percussive banks,
* Chip specific flags
*/
if(version >= 2)
{
/* Melodic banks meta-data */
final_size += (32 + 1 + 1) * file->banks_count_melodic;
/* Percussive banks meta-data */
final_size += (32 + 1 + 1) * file->banks_count_percussion;
}
if(version >= 2)
ins_size = WOPN_INST_SIZE_V2;
else
ins_size = WOPN_INST_SIZE_V1;
/* Melodic instruments */
final_size += (ins_size * 128) * file->banks_count_melodic;
/* Percussive instruments */
final_size += (ins_size * 128) * file->banks_count_percussion;
return final_size;
}
size_t WOPN_CalculateInstFileSize(OPNIFile *file, uint16_t version)
{
size_t final_size = 0;
size_t ins_size = 0;
if(version == 0)
version = wopn_latest_version;
if(!file)
return 0;
final_size += 11 + 1;
/*
* Magic number,
* is percussive instrument
*/
/* Version */
if (version > 1)
final_size += 2;
if(version > 1)
/* Skip sounding delays are not part of single-instrument file
* two sizes of uint16_t will be subtracted */
ins_size = WOPN_INST_SIZE_V2 - (sizeof(uint16_t) * 2);
else
ins_size = WOPN_INST_SIZE_V1;
final_size += ins_size;
return final_size;
}
int WOPN_SaveBankToMem(WOPNFile *file, void *dest_mem, size_t length, uint16_t version, uint16_t force_gm)
{
uint8_t *cursor = (uint8_t *)dest_mem;
uint16_t ins_size = 0;
uint16_t i, j, k;
uint16_t banks_melodic = force_gm ? 1 : file->banks_count_melodic;
uint16_t banks_percussive = force_gm ? 1 : file->banks_count_percussion;
WOPNBank *bankslots[2];
uint16_t bankslots_sizes[2];
if(version == 0)
version = wopn_latest_version;
#define GO_FORWARD(bytes) { cursor += bytes; length -= bytes; }
if(length < 11)
return WOPN_ERR_UNEXPECTED_ENDING;
if(version > 1)
memcpy(cursor, wopn2_magic2, 11);
else
memcpy(cursor, wopn2_magic1, 11);
GO_FORWARD(11);
if(version > 1)
{
if(length < 2)
return WOPN_ERR_UNEXPECTED_ENDING;
fromUint16LE(version, cursor);
GO_FORWARD(2);
}
if(length < 2)
return WOPN_ERR_UNEXPECTED_ENDING;
fromUint16BE(banks_melodic, cursor);
GO_FORWARD(2);
if(length < 2)
return WOPN_ERR_UNEXPECTED_ENDING;
fromUint16BE(banks_percussive, cursor);
GO_FORWARD(2);
if(length < 1)
return WOPN_ERR_UNEXPECTED_ENDING;
cursor[0] = file->lfo_freq;
GO_FORWARD(1);
bankslots[0] = file->banks_melodic;
bankslots_sizes[0] = banks_melodic;
bankslots[1] = file->banks_percussive;
bankslots_sizes[1] = banks_percussive;
if(version >= 2)
{
for(i = 0; i < 2; i++)
{
for(j = 0; j < bankslots_sizes[i]; j++)
{
if(length < 34)
return WOPN_ERR_UNEXPECTED_ENDING;
strncpy((char*)cursor, bankslots[i][j].bank_name, 32);
cursor[32] = bankslots[i][j].bank_midi_lsb;
cursor[33] = bankslots[i][j].bank_midi_msb;
GO_FORWARD(34);
}
}
}
{/* Write instruments data */
if(version >= 2)
ins_size = WOPN_INST_SIZE_V2;
else
ins_size = WOPN_INST_SIZE_V1;
for(i = 0; i < 2; i++)
{
if(length < (ins_size * 128) * (size_t)bankslots_sizes[i])
return WOPN_ERR_UNEXPECTED_ENDING;
for(j = 0; j < bankslots_sizes[i]; j++)
{
for(k = 0; k < 128; k++)
{
WOPNInstrument *ins = &bankslots[i][j].ins[k];
WOPN_writeInstrument(ins, cursor, version, 1);
GO_FORWARD(ins_size);
}
}
}
}
return WOPN_ERR_OK;
#undef GO_FORWARD
}
int WOPN_SaveInstToMem(OPNIFile *file, void *dest_mem, size_t length, uint16_t version)
{
uint8_t *cursor = (uint8_t *)dest_mem;
uint16_t ins_size;
if(!cursor)
return WOPN_ERR_NULL_POINTER;
if(version == 0)
version = wopn_latest_version;
#define GO_FORWARD(bytes) { cursor += bytes; length -= bytes; }
{/* Magic number */
if(length < 11)
return WOPN_ERR_UNEXPECTED_ENDING;
if(version > 1)
memcpy(cursor, opni_magic2, 11);
else
memcpy(cursor, opni_magic1, 11);
GO_FORWARD(11);
}
if (version > 1)
{/* Version code */
if(length < 2)
return WOPN_ERR_UNEXPECTED_ENDING;
fromUint16LE(version, cursor);
GO_FORWARD(2);
}
{/* is drum flag */
if(length < 1)
return WOPN_ERR_UNEXPECTED_ENDING;
*cursor = file->is_drum;
GO_FORWARD(1);
}
if(version > 1)
/* Skip sounding delays are not part of single-instrument file
* two sizes of uint16_t will be subtracted */
ins_size = WOPN_INST_SIZE_V2 - (sizeof(uint16_t) * 2);
else
ins_size = WOPN_INST_SIZE_V1;
if(length < ins_size)
return WOPN_ERR_UNEXPECTED_ENDING;
WOPN_writeInstrument(&file->inst, cursor, version, 0);
GO_FORWARD(ins_size);
return WOPN_ERR_OK;
#undef GO_FORWARD
}

View file

@ -0,0 +1,250 @@
/*
* Wohlstand's OPN2 Bank File - a bank format to store OPN2 timbre data and setup
*
* Copyright (c) 2018 Vitaly Novichkov <admin@wohlnet.ru>
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the "Software"),
* to deal in the Software without restriction, including without limitation the
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
* sell copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included
* in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
* OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
* DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
* ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
* DEALINGS IN THE SOFTWARE.
*/
#ifndef WOPN_FILE_H
#define WOPN_FILE_H
#include <stdint.h>
#include <stddef.h>
#ifdef __cplusplus
extern "C" {
#endif
#if !defined(__STDC_VERSION__) || (defined(__STDC_VERSION__) && (__STDC_VERSION__ < 199901L)) \
|| defined(__STRICT_ANSI__) || !defined(__cplusplus)
typedef signed char int8_t;
typedef unsigned char uint8_t;
typedef signed short int int16_t;
typedef unsigned short int uint16_t;
#endif
/* Volume scaling model implemented in the libOPNMIDI */
typedef enum WOPN_VolumeModel
{
WOPN_VM_Generic = 0
} WOPN_VolumeModel;
typedef enum WOPN_InstrumentFlags
{
/* Is pseudo eight-operator (two 4-operator voices) instrument */
WOPN_Ins_Pseudo8op = 0x01,
/* Is a blank instrument entry */
WOPN_Ins_IsBlank = 0x02,
/* Mask of the flags range */
WOPN_Ins_ALL_MASK = 0x03
} WOPN_InstrumentFlags;
/* Error codes */
typedef enum WOPN_ErrorCodes
{
WOPN_ERR_OK = 0,
/* Magic number is not maching */
WOPN_ERR_BAD_MAGIC,
/* Too short file */
WOPN_ERR_UNEXPECTED_ENDING,
/* Zero banks count */
WOPN_ERR_INVALID_BANKS_COUNT,
/* Version of file is newer than supported by current version of library */
WOPN_ERR_NEWER_VERSION,
/* Out of memory */
WOPN_ERR_OUT_OF_MEMORY,
/* Given null pointer memory data */
WOPN_ERR_NULL_POINTER
} WOPN_ErrorCodes;
/* OPN2 Oerators data */
typedef struct WOPNOperator
{
/* Detune and frequency multiplication register data */
uint8_t dtfm_30;
/* Total level register data */
uint8_t level_40;
/* Rate scale and attack register data */
uint8_t rsatk_50;
/* Amplitude modulation enable and Decay-1 register data */
uint8_t amdecay1_60;
/* Decay-2 register data */
uint8_t decay2_70;
/* Sustain and Release register data */
uint8_t susrel_80;
/* SSG-EG register data */
uint8_t ssgeg_90;
} WOPNOperator;
/* Instrument entry */
typedef struct WOPNInstrument
{
/* Title of the instrument */
char inst_name[34];
/* MIDI note key (half-tone) offset for an instrument (or a first voice in pseudo-4-op mode) */
int16_t note_offset;
/* Reserved */
int8_t midi_velocity_offset;
/* Percussion MIDI base tone number at which this drum will be played */
uint8_t percussion_key_number;
/* Enum WOPN_InstrumentFlags */
uint8_t inst_flags;
/* Feedback and Algorithm register data */
uint8_t fbalg;
/* LFO Sensitivity register data */
uint8_t lfosens;
/* Operators register data */
WOPNOperator operators[4];
/* Millisecond delay of sounding while key is on */
uint16_t delay_on_ms;
/* Millisecond delay of sounding after key off */
uint16_t delay_off_ms;
} WOPNInstrument;
/* Bank entry */
typedef struct WOPNBank
{
/* Name of bank */
char bank_name[33];
/* MIDI Bank LSB code */
uint8_t bank_midi_lsb;
/* MIDI Bank MSB code */
uint8_t bank_midi_msb;
/* Instruments data of this bank */
WOPNInstrument ins[128];
} WOPNBank;
/* Instrument data file */
typedef struct OPNIFile
{
/* Version of instrument file */
uint16_t version;
/* Is this a percussion instrument */
uint8_t is_drum;
/* Instrument data */
WOPNInstrument inst;
} OPNIFile;
/* Bank data file */
typedef struct WOPNFile
{
/* Version of bank file */
uint16_t version;
/* Count of melodic banks in this file */
uint16_t banks_count_melodic;
/* Count of percussion banks in this file */
uint16_t banks_count_percussion;
/* Chip global LFO enable flag and frequency register data */
uint8_t lfo_freq;
/* Reserved (Enum WOPN_VolumeModel) */
uint8_t volume_model;
/* dynamically allocated data Melodic banks array */
WOPNBank *banks_melodic;
/* dynamically allocated data Percussive banks array */
WOPNBank *banks_percussive;
} WOPNFile;
/**
* @brief Initialize blank WOPN data structure with allocated bank data
* @param melodic_banks Count of melodic banks
* @param percussive_banks Count of percussive banks
* @return pointer to heap-allocated WOPN data structure or NULL when out of memory or incorrectly given banks counts
*/
extern WOPNFile *WOPN_Init(uint16_t melodic_banks, uint16_t percussive_banks);
/**
* @brief Clean up WOPN data file (all allocated bank arrays will be fried too)
* @param file pointer to heap-allocated WOPN data structure
*/
extern void WOPN_Free(WOPNFile *file);
/**
* @brief Compare two bank entries
* @param bank1 First bank
* @param bank2 Second bank
* @return 1 if banks are equal or 0 if there are different
*/
extern int WOPN_BanksCmp(const WOPNFile *bank1, const WOPNFile *bank2);
/**
* @brief Load WOPN bank file from the memory.
* WOPN data structure will be allocated. (don't forget to clear it with WOPN_Free() after use!)
* @param mem Pointer to memory block contains raw WOPN bank file data
* @param length Length of given memory block
* @param error pointer to integer to return an error code. Pass NULL if you don't want to use error codes.
* @return Heap-allocated WOPN file data structure or NULL if any error has occouped
*/
extern WOPNFile *WOPN_LoadBankFromMem(void *mem, size_t length, int *error);
/**
* @brief Load WOPI instrument file from the memory.
* You must allocate OPNIFile structure by yourself and give the pointer to it.
* @param file Pointer to destinition OPNIFile structure to fill it with parsed data.
* @param mem Pointer to memory block contains raw WOPI instrument file data
* @param length Length of given memory block
* @return 0 if no errors occouped, or an error code of WOPN_ErrorCodes enumeration
*/
extern int WOPN_LoadInstFromMem(OPNIFile *file, void *mem, size_t length);
/**
* @brief Calculate the size of the output memory block
* @param file Heap-allocated WOPN file data structure
* @param version Destinition version of the file
* @return Size of the raw WOPN file data
*/
extern size_t WOPN_CalculateBankFileSize(WOPNFile *file, uint16_t version);
/**
* @brief Calculate the size of the output memory block
* @param file Pointer to WOPI file data structure
* @param version Destinition version of the file
* @return Size of the raw WOPI file data
*/
extern size_t WOPN_CalculateInstFileSize(OPNIFile *file, uint16_t version);
/**
* @brief Write raw WOPN into given memory block
* @param file Heap-allocated WOPN file data structure
* @param dest_mem Destinition memory block pointer
* @param length Length of destinition memory block
* @param version Wanted WOPN version
* @param force_gm Force GM set in saved bank file
* @return Error code or 0 on success
*/
extern int WOPN_SaveBankToMem(WOPNFile *file, void *dest_mem, size_t length, uint16_t version, uint16_t force_gm);
/**
* @brief Write raw WOPI into given memory block
* @param file Pointer to WOPI file data structure
* @param dest_mem Destinition memory block pointer
* @param length Length of destinition memory block
* @param version Wanted WOPI version
* @return Error code or 0 on success
*/
extern int WOPN_SaveInstToMem(OPNIFile *file, void *dest_mem, size_t length, uint16_t version);
#ifdef __cplusplus
}
#endif
#endif /* WOPN_FILE_H */

Some files were not shown because too many files have changed in this diff Show more