gzdoom-gles/libraries/zmusic/mididevices/music_opl_mididevice.cpp
Christoph Oelckers 5743a56ef4 - created a new zmusic library which will eventually contain all the music playback code.
Currently all it contains are the MIDI sources and the MIDI devices, the rest needs to be reworked first.

# Conflicts:
#	libraries/zmusic/i_module.cpp
#	libraries/zmusic/i_module.h
#	src/CMakeLists.txt
#	src/i_module.cpp
#	src/i_module.h
#	src/sound/music/midi_cvars.cpp
#	src/utility/i_module.cpp
#	src/utility/i_module.h

# Conflicts:
#	src/CMakeLists.txt
#	src/sound/musicformats/music_opl.cpp
2020-01-05 11:46:20 +01:00

320 lines
9.4 KiB
C++

/*
** music_opl_mididevice.cpp
** Provides an emulated OPL implementation of a MIDI output device.
**
**---------------------------------------------------------------------------
** Copyright 2008 Randy Heit
** All rights reserved.
**
** Redistribution and use in source and binary forms, with or without
** modification, are permitted provided that the following conditions
** are met:
**
** 1. Redistributions of source code must retain the above copyright
** notice, this list of conditions and the following disclaimer.
** 2. Redistributions in binary form must reproduce the above copyright
** notice, this list of conditions and the following disclaimer in the
** documentation and/or other materials provided with the distribution.
** 3. The name of the author may not be used to endorse or promote products
** derived from this software without specific prior written permission.
**
** THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
** IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
** OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
** IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
** INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
** NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
** THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
**---------------------------------------------------------------------------
**
**
*/
// HEADER FILES ------------------------------------------------------------
#include "mididevice.h"
#include "zmusic/mus2midi.h"
#include "oplsynth/opl.h"
#include "oplsynth/opl_mus_player.h"
// MACROS ------------------------------------------------------------------
// EXTERNAL FUNCTION PROTOTYPES --------------------------------------------
// PUBLIC FUNCTION PROTOTYPES ----------------------------------------------
// PRIVATE FUNCTION PROTOTYPES ---------------------------------------------
// EXTERNAL DATA DECLARATIONS ----------------------------------------------
// PRIVATE DATA DEFINITIONS ------------------------------------------------
// OPL implementation of a MIDI output device -------------------------------
class OPLMIDIDevice : public SoftSynthMIDIDevice, protected OPLmusicBlock
{
public:
OPLMIDIDevice(const OPLMidiConfig *config);
int OpenRenderer();
void Close();
int GetTechnology() const;
std::string GetStats() override;
protected:
void CalcTickRate();
int PlayTick();
void HandleEvent(int status, int parm1, int parm2);
void HandleLongEvent(const uint8_t *data, int len);
void ComputeOutput(float *buffer, int len);
bool ServiceStream(void *buff, int numbytes);
int GetDeviceType() const override { return MDEV_OPL; }
};
// PUBLIC DATA DEFINITIONS -------------------------------------------------
// CODE --------------------------------------------------------------------
//==========================================================================
//
// OPLMIDIDevice Contructor
//
//==========================================================================
OPLMIDIDevice::OPLMIDIDevice(const OPLMidiConfig *config)
: SoftSynthMIDIDevice((int)OPL_SAMPLE_RATE), OPLmusicBlock(config->core, config->numchips)
{
FullPan = config->fullpan;
memcpy(OPLinstruments, config->OPLinstruments, sizeof(OPLinstruments));
StreamBlockSize = 14;
}
//==========================================================================
//
// OPLMIDIDevice :: Open
//
// Returns 0 on success.
//
//==========================================================================
int OPLMIDIDevice::OpenRenderer()
{
if (io == NULL || 0 == (NumChips = io->Init(currentCore, NumChips, FullPan, true)))
{
return 1;
}
isMono = !FullPan && !io->IsOPL3;
stopAllVoices();
resetAllControllers(100);
return 0;
}
//==========================================================================
//
// OPLMIDIDevice :: Close
//
//==========================================================================
void OPLMIDIDevice::Close()
{
SoftSynthMIDIDevice::Close();
io->Reset();
}
//==========================================================================
//
// OPLMIDIDevice :: GetTechnology
//
//==========================================================================
int OPLMIDIDevice::GetTechnology() const
{
return MIDIDEV_FMSYNTH;
}
//==========================================================================
//
// OPLMIDIDevice :: CalcTickRate
//
// Tempo is the number of microseconds per quarter note.
// Division is the number of ticks per quarter note.
//
//==========================================================================
void OPLMIDIDevice::CalcTickRate()
{
SoftSynthMIDIDevice::CalcTickRate();
io->SetClockRate(OPLmusicBlock::SamplesPerTick = SoftSynthMIDIDevice::SamplesPerTick);
}
//==========================================================================
//
// OPLMIDIDevice :: PlayTick
//
// We derive from two base classes that both define PlayTick(), so we need
// to be unambiguous about which one to use.
//
//==========================================================================
int OPLMIDIDevice::PlayTick()
{
return SoftSynthMIDIDevice::PlayTick();
}
//==========================================================================
//
// OPLMIDIDevice :: HandleEvent
//
// Processes a normal MIDI event.
//
//==========================================================================
void OPLMIDIDevice::HandleEvent(int status, int parm1, int parm2)
{
int command = status & 0xF0;
int channel = status & 0x0F;
// Swap voices 9 and 15, because their roles are reversed
// in MUS and MIDI formats.
if (channel == 9)
{
channel = 15;
}
else if (channel == 15)
{
channel = 9;
}
switch (command)
{
case MIDI_NOTEOFF:
playingcount--;
noteOff(channel, parm1);
break;
case MIDI_NOTEON:
playingcount++;
noteOn(channel, parm1, parm2);
break;
case MIDI_POLYPRESS:
//DEBUGOUT("Unhandled note aftertouch: Channel %d, note %d, value %d\n", channel, parm1, parm2);
break;
case MIDI_CTRLCHANGE:
switch (parm1)
{
// some controllers here get passed on but are not handled by the player.
//case 0: changeBank(channel, parm2); break;
case 1: changeModulation(channel, parm2); break;
case 6: changeExtended(channel, ctrlDataEntryHi, parm2); break;
case 7: changeVolume(channel, parm2, false); break;
case 10: changePanning(channel, parm2); break;
case 11: changeVolume(channel, parm2, true); break;
case 38: changeExtended(channel, ctrlDataEntryLo, parm2); break;
case 64: changeSustain(channel, parm2); break;
//case 67: changeSoftPedal(channel, parm2); break;
//case 91: changeReverb(channel, parm2); break;
//case 93: changeChorus(channel, parm2); break;
case 98: changeExtended(channel, ctrlNRPNLo, parm2); break;
case 99: changeExtended(channel, ctrlNRPNHi, parm2); break;
case 100: changeExtended(channel, ctrlRPNLo, parm2); break;
case 101: changeExtended(channel, ctrlRPNHi, parm2); break;
case 120: allNotesOff(channel, parm2); break;
case 121: resetControllers(channel, 100); break;
case 123: notesOff(channel, parm2); break;
//case 126: changeMono(channel, parm2); break;
//case 127: changePoly(channel, parm2); break;
default:
//DEBUGOUT("Unhandled controller: Channel %d, controller %d, value %d\n", channel, parm1, parm2);
break;
}
break;
case MIDI_PRGMCHANGE:
programChange(channel, parm1);
break;
case MIDI_CHANPRESS:
//DEBUGOUT("Unhandled channel aftertouch: Channel %d, value %d\n", channel, parm1, 0);
break;
case MIDI_PITCHBEND:
changePitch(channel, parm1, parm2);
break;
}
}
//==========================================================================
//
// OPLMIDIDevice :: HandleLongEvent
//
//==========================================================================
void OPLMIDIDevice::HandleLongEvent(const uint8_t *data, int len)
{
}
//==========================================================================
//
// OPLMIDIDevice :: ComputeOutput
//
// We override ServiceStream, so this function is never actually called.
//
//==========================================================================
void OPLMIDIDevice::ComputeOutput(float *buffer, int len)
{
}
//==========================================================================
//
// OPLMIDIDevice :: ServiceStream
//
//==========================================================================
bool OPLMIDIDevice::ServiceStream(void *buff, int numbytes)
{
return OPLmusicBlock::ServiceStream(buff, numbytes);
}
//==========================================================================
//
// OPLMIDIDevice :: GetStats
//
//==========================================================================
std::string OPLMIDIDevice::GetStats()
{
std::string out;
for (uint32_t i = 0; i < io->NumChannels; ++i)
{
char star = '*';
if (voices[i].index == ~0u)
{
star = '/';
}
else if (voices[i].sustained)
{
star = '-';
}
else if (voices[i].current_instr_voice == &voices[i].current_instr->voices[1])
{
star = '\\';
}
else
{
star = '+';
}
out += star;
}
return out;
}
MIDIDevice* CreateOplMIDIDevice(const OPLMidiConfig* config)
{
return new OPLMIDIDevice(config);
}