mirror of
https://github.com/ZDoom/qzdoom.git
synced 2024-11-10 06:42:12 +00:00
- Support for MIDI on Linux via alsa sequencer
This commit is contained in:
parent
118e3db5ca
commit
ea1b8de405
13 changed files with 795 additions and 22 deletions
|
@ -10,6 +10,11 @@ endif()
|
|||
|
||||
include( CheckFunctionExists )
|
||||
|
||||
find_package( ALSA )
|
||||
if (WIN32 OR ALSA_FOUND)
|
||||
add_definitions( -DHAVE_SYSTEM_MIDI )
|
||||
endif()
|
||||
|
||||
if( DYN_SNDFILE)
|
||||
add_definitions( -DHAVE_SNDFILE -DDYN_SNDFILE )
|
||||
else()
|
||||
|
@ -44,13 +49,21 @@ endif()
|
|||
include_directories( "${CMAKE_CURRENT_SOURCE_DIR}/../libraries/dumb/include" "${ZLIB_INCLUDE_DIR}" "${ADL_INCLUDE_DIR}" "${OPN_INCLUDE_DIR}" "${TIMIDITYPP_INCLUDE_DIR}" "${TIMIDITY_INCLUDE_DIR}" "${WILDMIDI_INCLUDE_DIR}" "${OPLSYNTH_INCLUDE_DIR}" "${GME_INCLUDE_DIR}" "${CMAKE_CURRENT_SOURCE_DIR}" )
|
||||
|
||||
if (WIN32)
|
||||
set( PLAT_SOURCES
|
||||
mididevices/music_win_mididevice.cpp
|
||||
musicformats/win32/i_cd.cpp
|
||||
musicformats/win32/helperthread.cpp
|
||||
)
|
||||
set( PLAT_SOURCES
|
||||
mididevices/music_win_mididevice.cpp
|
||||
musicformats/win32/i_cd.cpp
|
||||
musicformats/win32/helperthread.cpp
|
||||
)
|
||||
elseif(${CMAKE_SYSTEM_NAME} STREQUAL "Linux")
|
||||
if (ALSA_FOUND)
|
||||
set( PLAT_SOURCES
|
||||
mididevices/music_alsa_mididevice.cpp
|
||||
mididevices/music_alsa_state.cpp
|
||||
)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
|
||||
file( GLOB HEADER_FILES
|
||||
zmusic/*.h
|
||||
mididevices/*.h
|
||||
|
@ -111,6 +124,11 @@ if( NOT DYN_FLUIDSYNTH AND FLUIDSYNTH_FOUND )
|
|||
target_link_libraries( zmusic ${FLUIDSYNTH_LIBRARIES} )
|
||||
endif()
|
||||
|
||||
if(ALSA_FOUND)
|
||||
include_directories( "${ALSA_INCLUDE_DIR}" )
|
||||
target_link_libraries( zmusic ${ALSA_LIBRARIES} )
|
||||
endif()
|
||||
|
||||
source_group("MIDI Devices" REGULAR_EXPRESSION "^${CMAKE_CURRENT_SOURCE_DIR}/mididevices/.+")
|
||||
source_group("MIDI Sources" REGULAR_EXPRESSION "^${CMAKE_CURRENT_SOURCE_DIR}/midisources/.+")
|
||||
source_group("Music Formats" REGULAR_EXPRESSION "^${CMAKE_CURRENT_SOURCE_DIR}/musicformats/.+")
|
||||
|
|
|
@ -154,3 +154,6 @@ MIDIDevice *CreateWildMIDIDevice(const char *Args, int samplerate);
|
|||
MIDIDevice* CreateWinMIDIDevice(int mididevice);
|
||||
#endif
|
||||
|
||||
#ifdef __linux__
|
||||
MIDIDevice* CreateAlsaMIDIDevice(int mididevice);
|
||||
#endif
|
||||
|
|
434
libraries/zmusic/mididevices/music_alsa_mididevice.cpp
Normal file
434
libraries/zmusic/mididevices/music_alsa_mididevice.cpp
Normal file
|
@ -0,0 +1,434 @@
|
|||
/*
|
||||
** Provides an ALSA implementation of a MIDI output device.
|
||||
**
|
||||
**---------------------------------------------------------------------------
|
||||
** Copyright 2008-2010 Randy Heit
|
||||
** Copyright 2020 Petr Mrazek
|
||||
** 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.
|
||||
**---------------------------------------------------------------------------
|
||||
**
|
||||
*/
|
||||
|
||||
#if defined __linux__ && defined HAVE_SYSTEM_MIDI
|
||||
|
||||
#include <algorithm>
|
||||
#include <memory>
|
||||
#include <assert.h>
|
||||
#include <thread>
|
||||
#include <pthread.h>
|
||||
#include <atomic>
|
||||
#include <cstring>
|
||||
|
||||
#include "mididevice.h"
|
||||
#include "zmusic/m_swap.h"
|
||||
#include "zmusic/mus2midi.h"
|
||||
|
||||
#include "music_alsa_state.h"
|
||||
#include <alsa/asoundlib.h>
|
||||
|
||||
namespace {
|
||||
|
||||
class AlsaMIDIDevice : public MIDIDevice
|
||||
{
|
||||
public:
|
||||
AlsaMIDIDevice(int dev_id);
|
||||
~AlsaMIDIDevice();
|
||||
int Open() override;
|
||||
void Close() override;
|
||||
bool IsOpen() const override;
|
||||
int GetTechnology() const override;
|
||||
int SetTempo(int tempo) override;
|
||||
int SetTimeDiv(int timediv) override;
|
||||
int StreamOut(MidiHeader *data) override;
|
||||
int StreamOutSync(MidiHeader *data) override;
|
||||
int Resume() override;
|
||||
void Stop() override;
|
||||
|
||||
bool FakeVolume() override {
|
||||
// Not sure if we even can control the volume this way with Alsa, so make it fake.
|
||||
return true;
|
||||
};
|
||||
|
||||
bool Pause(bool paused) override;
|
||||
void InitPlayback() override;
|
||||
bool Update() override;
|
||||
void PrecacheInstruments(const uint16_t *instruments, int count) override {}
|
||||
|
||||
bool CanHandleSysex() const override
|
||||
{
|
||||
// Assume we can, let Alsa sort it out. We do not truly have full control.
|
||||
return true;
|
||||
}
|
||||
|
||||
void HandleTempoChange(int tick, int tempo);
|
||||
void HandleEvent(int tick, int status, int parm1, int parm2);
|
||||
void HandleLongEvent(int tick, const uint8_t *data, int len);
|
||||
void SendStopEvents();
|
||||
void PumpEvents();
|
||||
|
||||
|
||||
protected:
|
||||
AlsaSequencer &sequencer;
|
||||
|
||||
MidiHeader *Events = nullptr;
|
||||
bool Started = false;
|
||||
uint32_t Position = 0;
|
||||
|
||||
const static int IntendedPortId = 0;
|
||||
bool Connected = false;
|
||||
int PortId = -1;
|
||||
int QueueId = -1;
|
||||
|
||||
int DestinationClientId;
|
||||
int DestinationPortId;
|
||||
|
||||
int Tempo = 480000;
|
||||
int TimeDiv = 480;
|
||||
|
||||
std::thread PlayerThread;
|
||||
std::atomic<bool> Exit;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
AlsaMIDIDevice::AlsaMIDIDevice(int dev_id) : sequencer(AlsaSequencer::Get())
|
||||
{
|
||||
auto & internalDevices = sequencer.GetInternalDevices();
|
||||
auto & device = internalDevices.at(dev_id);
|
||||
DestinationClientId = device.ClientID;
|
||||
DestinationPortId = device.PortNumber;
|
||||
}
|
||||
|
||||
AlsaMIDIDevice::~AlsaMIDIDevice()
|
||||
{
|
||||
Close();
|
||||
}
|
||||
|
||||
int AlsaMIDIDevice::Open()
|
||||
{
|
||||
if (!sequencer.IsOpen()) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
if(PortId < 0)
|
||||
{
|
||||
snd_seq_port_info_t *pinfo;
|
||||
snd_seq_port_info_alloca(&pinfo);
|
||||
|
||||
snd_seq_port_info_set_port(pinfo, IntendedPortId);
|
||||
snd_seq_port_info_set_port_specified(pinfo, 1);
|
||||
|
||||
snd_seq_port_info_set_name(pinfo, "GZDoom Music");
|
||||
|
||||
snd_seq_port_info_set_capability(pinfo, 0);
|
||||
snd_seq_port_info_set_type(pinfo, SND_SEQ_PORT_TYPE_MIDI_GENERIC | SND_SEQ_PORT_TYPE_APPLICATION);
|
||||
|
||||
int err = 0;
|
||||
err = snd_seq_create_port(sequencer.handle, pinfo);
|
||||
PortId = IntendedPortId;
|
||||
}
|
||||
|
||||
if (QueueId < 0)
|
||||
{
|
||||
QueueId = snd_seq_alloc_named_queue(sequencer.handle, "GZDoom Queue");
|
||||
}
|
||||
|
||||
if (!Connected) {
|
||||
Connected = (snd_seq_connect_to(sequencer.handle, PortId, DestinationClientId, DestinationPortId) == 0);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
void AlsaMIDIDevice::Close()
|
||||
{
|
||||
if(Connected) {
|
||||
snd_seq_disconnect_to(sequencer.handle, PortId, DestinationClientId, DestinationPortId);
|
||||
Connected = false;
|
||||
}
|
||||
if(QueueId >= 0) {
|
||||
snd_seq_free_queue(sequencer.handle, QueueId);
|
||||
QueueId = -1;
|
||||
}
|
||||
if(PortId >= 0) {
|
||||
snd_seq_delete_port(sequencer.handle, PortId);
|
||||
PortId = -1;
|
||||
}
|
||||
}
|
||||
|
||||
bool AlsaMIDIDevice::IsOpen() const
|
||||
{
|
||||
return Connected;
|
||||
}
|
||||
|
||||
int AlsaMIDIDevice::GetTechnology() const
|
||||
{
|
||||
// TODO: implement properly, for now assume everything is an external MIDI device
|
||||
return MIDIDEV_MIDIPORT;
|
||||
}
|
||||
|
||||
int AlsaMIDIDevice::SetTempo(int tempo)
|
||||
{
|
||||
Tempo = tempo;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int AlsaMIDIDevice::SetTimeDiv(int timediv)
|
||||
{
|
||||
TimeDiv = timediv;
|
||||
return 0;
|
||||
}
|
||||
|
||||
void AlsaMIDIDevice::HandleEvent(int tick, int status, int parm1, int parm2)
|
||||
{
|
||||
int command = status & 0xF0;
|
||||
int channel = status & 0x0F;
|
||||
|
||||
snd_seq_event_t ev;
|
||||
snd_seq_ev_clear(&ev);
|
||||
snd_seq_ev_set_source(&ev, PortId);
|
||||
snd_seq_ev_set_subs(&ev);
|
||||
snd_seq_ev_schedule_tick(&ev, QueueId, false, tick);
|
||||
|
||||
switch (command)
|
||||
{
|
||||
case MIDI_NOTEOFF:
|
||||
snd_seq_ev_set_noteoff(&ev, channel, parm1, parm2);
|
||||
break;
|
||||
|
||||
case MIDI_NOTEON:
|
||||
snd_seq_ev_set_noteon(&ev, channel, parm1, parm2);
|
||||
break;
|
||||
|
||||
case MIDI_POLYPRESS:
|
||||
// FIXME: Seems to be missing in the Alsa sequencer implementation
|
||||
return;
|
||||
|
||||
case MIDI_CTRLCHANGE:
|
||||
snd_seq_ev_set_controller(&ev, channel, parm1, parm2);
|
||||
break;
|
||||
|
||||
case MIDI_PRGMCHANGE:
|
||||
snd_seq_ev_set_pgmchange(&ev, channel, parm1);
|
||||
break;
|
||||
|
||||
case MIDI_CHANPRESS:
|
||||
snd_seq_ev_set_chanpress(&ev, channel, parm1);
|
||||
break;
|
||||
|
||||
case MIDI_PITCHBEND: {
|
||||
long bend = ((long)parm1 + (long)(parm2 << 7)) - 0x2000;
|
||||
snd_seq_ev_set_pitchbend(&ev, channel, bend);
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
return;
|
||||
}
|
||||
snd_seq_event_output(sequencer.handle, &ev);
|
||||
}
|
||||
|
||||
void AlsaMIDIDevice::HandleLongEvent(int tick, const uint8_t *data, int len)
|
||||
{
|
||||
// SysEx messages...
|
||||
if (len > 1 && (data[0] == 0xF0 || data[0] == 0xF7))
|
||||
{
|
||||
snd_seq_event_t ev;
|
||||
snd_seq_ev_clear(&ev);
|
||||
snd_seq_ev_set_source(&ev, PortId);
|
||||
snd_seq_ev_set_subs(&ev);
|
||||
snd_seq_ev_schedule_tick(&ev, QueueId, false, tick);
|
||||
snd_seq_ev_set_sysex(&ev, len, (void *)data);
|
||||
snd_seq_event_output(sequencer.handle, &ev);
|
||||
}
|
||||
}
|
||||
|
||||
void AlsaMIDIDevice::HandleTempoChange(int tick, int tempo) {
|
||||
if(Tempo != tempo) {
|
||||
Tempo = tempo;
|
||||
snd_seq_event_t ev;
|
||||
snd_seq_ev_clear(&ev);
|
||||
snd_seq_ev_set_source(&ev, PortId);
|
||||
snd_seq_ev_set_subs(&ev);
|
||||
snd_seq_ev_schedule_tick(&ev, QueueId, false, tick);
|
||||
snd_seq_change_queue_tempo(sequencer.handle, QueueId, Tempo, &ev);
|
||||
snd_seq_event_output(sequencer.handle, &ev);
|
||||
}
|
||||
}
|
||||
|
||||
void AlsaMIDIDevice::PumpEvents() {
|
||||
int error = 0;
|
||||
snd_seq_queue_tempo_t *tempo;
|
||||
snd_seq_queue_tempo_alloca(&tempo);
|
||||
snd_seq_queue_tempo_set_tempo(tempo, Tempo);
|
||||
snd_seq_queue_tempo_set_ppq(tempo, TimeDiv);
|
||||
error = snd_seq_set_queue_tempo(sequencer.handle, QueueId, tempo);
|
||||
|
||||
snd_seq_start_queue(sequencer.handle, QueueId, NULL);
|
||||
error = snd_seq_drain_output(sequencer.handle);
|
||||
|
||||
int running_time = 0;
|
||||
while (!Exit) {
|
||||
if(!Events) {
|
||||
// NOTE: in practice, this is never reached. however, if it were, it would prevent crashes below.
|
||||
continue;
|
||||
}
|
||||
|
||||
uint32_t *event = (uint32_t *)(Events->lpData + Position);
|
||||
int ticks = event[0];
|
||||
running_time += ticks;
|
||||
if (MEVENT_EVENTTYPE(event[2]) == MEVENT_TEMPO) {
|
||||
HandleTempoChange(running_time, MEVENT_EVENTPARM(event[2]));
|
||||
}
|
||||
else if (MEVENT_EVENTTYPE(event[2]) == MEVENT_LONGMSG) {
|
||||
HandleLongEvent(running_time, (uint8_t *)&event[3], MEVENT_EVENTPARM(event[2]));
|
||||
}
|
||||
else if (MEVENT_EVENTTYPE(event[2]) == 0) {
|
||||
// Short MIDI event
|
||||
int status = event[2] & 0xff;
|
||||
int parm1 = (event[2] >> 8) & 0x7f;
|
||||
int parm2 = (event[2] >> 16) & 0x7f;
|
||||
HandleEvent(running_time, status, parm1, parm2);
|
||||
}
|
||||
|
||||
// Advance to next event.
|
||||
if (event[2] < 0x80000000)
|
||||
{ // Short message
|
||||
Position += 12;
|
||||
}
|
||||
else
|
||||
{ // Long message
|
||||
Position += 12 + ((MEVENT_EVENTPARM(event[2]) + 3) & ~3);
|
||||
}
|
||||
|
||||
// Did we use up this buffer?
|
||||
if (Position >= Events->dwBytesRecorded)
|
||||
{
|
||||
Events = Events->lpNext;
|
||||
Position = 0;
|
||||
|
||||
if (Callback != NULL)
|
||||
{
|
||||
Callback(CallbackData);
|
||||
}
|
||||
snd_seq_drain_output(sequencer.handle);
|
||||
snd_seq_sync_output_queue(sequencer.handle);
|
||||
}
|
||||
}
|
||||
// Send stop events, just to be sure we don't end up with stuck notes
|
||||
{
|
||||
snd_seq_drop_output(sequencer.handle);
|
||||
SendStopEvents();
|
||||
// FIXME: attach to a timestamped event and make it go through the queue?
|
||||
snd_seq_stop_queue(sequencer.handle, QueueId, NULL);
|
||||
snd_seq_drain_output(sequencer.handle);
|
||||
snd_seq_sync_output_queue(sequencer.handle);
|
||||
}
|
||||
}
|
||||
|
||||
void AlsaMIDIDevice::SendStopEvents() {
|
||||
// NOTE: for some reason, the midi streamer doesn't send us these.
|
||||
for (int channel = 0; channel < 16; ++channel)
|
||||
{
|
||||
snd_seq_event_t ev;
|
||||
snd_seq_ev_clear(&ev);
|
||||
snd_seq_ev_set_source(&ev, PortId);
|
||||
snd_seq_ev_set_subs(&ev);
|
||||
snd_seq_ev_schedule_tick(&ev, QueueId, true, 0);
|
||||
snd_seq_ev_set_controller(&ev, channel, MIDI_CTL_ALL_NOTES_OFF, 0);
|
||||
snd_seq_event_output(sequencer.handle, &ev);
|
||||
snd_seq_ev_set_controller(&ev, channel, MIDI_CTL_RESET_CONTROLLERS, 0);
|
||||
snd_seq_event_output(sequencer.handle, &ev);
|
||||
}
|
||||
snd_seq_drain_output(sequencer.handle);
|
||||
snd_seq_sync_output_queue(sequencer.handle);
|
||||
}
|
||||
|
||||
int AlsaMIDIDevice::Resume()
|
||||
{
|
||||
if(!Connected) {
|
||||
return 1;
|
||||
}
|
||||
Exit = false;
|
||||
PlayerThread = std::thread(&AlsaMIDIDevice::PumpEvents, this);
|
||||
return 0;
|
||||
}
|
||||
|
||||
void AlsaMIDIDevice::InitPlayback()
|
||||
{
|
||||
Exit = false;
|
||||
}
|
||||
|
||||
void AlsaMIDIDevice::Stop()
|
||||
{
|
||||
/*
|
||||
* NOTE: this is slow. Maybe we should just leave the thread be and let it asynchronously drain in the background.
|
||||
*/
|
||||
Exit = true;
|
||||
PlayerThread.join();
|
||||
}
|
||||
|
||||
bool AlsaMIDIDevice::Pause(bool paused)
|
||||
{
|
||||
// TODO: implement
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
int AlsaMIDIDevice::StreamOut(MidiHeader *header)
|
||||
{
|
||||
header->lpNext = NULL;
|
||||
if (Events == NULL)
|
||||
{
|
||||
Events = header;
|
||||
Position = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
MidiHeader **p;
|
||||
|
||||
for (p = &Events; *p != NULL; p = &(*p)->lpNext)
|
||||
{ }
|
||||
*p = header;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
int AlsaMIDIDevice::StreamOutSync(MidiHeader *header)
|
||||
{
|
||||
return StreamOut(header);
|
||||
}
|
||||
|
||||
bool AlsaMIDIDevice::Update()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
MIDIDevice *CreateAlsaMIDIDevice(int mididevice)
|
||||
{
|
||||
return new AlsaMIDIDevice(mididevice);
|
||||
}
|
||||
#endif
|
166
libraries/zmusic/mididevices/music_alsa_state.cpp
Normal file
166
libraries/zmusic/mididevices/music_alsa_state.cpp
Normal file
|
@ -0,0 +1,166 @@
|
|||
/*
|
||||
** Provides an implementation of an ALSA sequencer wrapper
|
||||
**
|
||||
**---------------------------------------------------------------------------
|
||||
** Copyright 2020 Petr Mrazek
|
||||
** 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.
|
||||
**---------------------------------------------------------------------------
|
||||
**
|
||||
*/
|
||||
#include "music_alsa_state.h"
|
||||
|
||||
#if defined __linux__ && defined HAVE_SYSTEM_MIDI
|
||||
|
||||
#include <alsa/asoundlib.h>
|
||||
#include <sstream>
|
||||
|
||||
AlsaSequencer & AlsaSequencer::Get() {
|
||||
static AlsaSequencer sequencer;
|
||||
return sequencer;
|
||||
}
|
||||
|
||||
AlsaSequencer::AlsaSequencer() {
|
||||
Open();
|
||||
}
|
||||
|
||||
AlsaSequencer::~AlsaSequencer() {
|
||||
Close();
|
||||
}
|
||||
|
||||
bool AlsaSequencer::Open() {
|
||||
error = snd_seq_open(&handle, "default", SND_SEQ_OPEN_DUPLEX, 0);
|
||||
if(error) {
|
||||
return false;
|
||||
}
|
||||
error = snd_seq_set_client_name(handle, "GZDoom");
|
||||
if(error) {
|
||||
snd_seq_close(handle);
|
||||
handle = nullptr;
|
||||
return false;
|
||||
}
|
||||
OurId = snd_seq_client_id(handle);
|
||||
if (OurId < 0) {
|
||||
error = OurId;
|
||||
OurId = -1;
|
||||
|
||||
snd_seq_close(handle);
|
||||
handle = nullptr;
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
void AlsaSequencer::Close() {
|
||||
if(!handle) {
|
||||
return;
|
||||
}
|
||||
snd_seq_close(handle);
|
||||
handle = nullptr;
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
||||
bool filter(snd_seq_port_info_t *pinfo)
|
||||
{
|
||||
int capability = snd_seq_port_info_get_capability(pinfo);
|
||||
if(capability & SND_SEQ_PORT_CAP_NO_EXPORT) {
|
||||
return false;
|
||||
}
|
||||
const int writable = (SND_SEQ_PORT_CAP_WRITE|SND_SEQ_PORT_CAP_SUBS_WRITE);
|
||||
if((capability & writable) != writable) {
|
||||
return false;
|
||||
}
|
||||
int type = snd_seq_port_info_get_type(pinfo);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
int AlsaSequencer::EnumerateDevices() {
|
||||
if(!handle) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
snd_seq_client_info_t *cinfo;
|
||||
snd_seq_port_info_t *pinfo;
|
||||
|
||||
snd_seq_client_info_alloca(&cinfo);
|
||||
snd_seq_port_info_alloca(&pinfo);
|
||||
|
||||
int index = 0;
|
||||
|
||||
// enumerate clients
|
||||
snd_seq_client_info_set_client(cinfo, -1);
|
||||
while (snd_seq_query_next_client(handle, cinfo) >= 0) {
|
||||
snd_seq_port_info_set_client(pinfo, snd_seq_client_info_get_client(cinfo));
|
||||
|
||||
// Ignore 'ALSA oddities' that we don't want to use
|
||||
int clientID = snd_seq_client_info_get_client(cinfo);
|
||||
if(clientID < 16) {
|
||||
continue;
|
||||
}
|
||||
|
||||
snd_seq_port_info_set_port(pinfo, -1);
|
||||
// enumerate ports
|
||||
while (snd_seq_query_next_port(handle, pinfo) >= 0) {
|
||||
if (!filter(pinfo)) {
|
||||
continue;
|
||||
}
|
||||
externalDevices.emplace_back();
|
||||
internalDevices.emplace_back();
|
||||
|
||||
auto & item = externalDevices.back();
|
||||
auto & itemInternal = internalDevices.back();
|
||||
itemInternal.ID = item.ID = index++;
|
||||
const char *name = snd_seq_port_info_get_name(pinfo);
|
||||
int portNumber = snd_seq_port_info_get_port(pinfo);
|
||||
if(!name) {
|
||||
std::ostringstream out;
|
||||
out << "MIDI Port " << clientID << ":" << portNumber;
|
||||
itemInternal.Name = item.Name = out.str();
|
||||
}
|
||||
else {
|
||||
itemInternal.Name = item.Name = name;
|
||||
}
|
||||
itemInternal.ClientID = clientID;
|
||||
itemInternal.PortNumber = portNumber;
|
||||
itemInternal.type = snd_seq_port_info_get_type(pinfo);
|
||||
}
|
||||
}
|
||||
return index;
|
||||
}
|
||||
|
||||
const std::vector<MidiOutDeviceInternal> & AlsaSequencer::GetInternalDevices()
|
||||
{
|
||||
return internalDevices;
|
||||
}
|
||||
|
||||
const std::vector<MidiOutDevice> & AlsaSequencer::GetDevices() {
|
||||
return externalDevices;
|
||||
}
|
||||
|
||||
#endif
|
81
libraries/zmusic/mididevices/music_alsa_state.h
Normal file
81
libraries/zmusic/mididevices/music_alsa_state.h
Normal file
|
@ -0,0 +1,81 @@
|
|||
/*
|
||||
** Provides an implementation of an ALSA sequencer wrapper
|
||||
**
|
||||
**---------------------------------------------------------------------------
|
||||
** Copyright 2008-2010 Randy Heit
|
||||
** Copyright 2020 Petr Mrazek
|
||||
** 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.
|
||||
**---------------------------------------------------------------------------
|
||||
**
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#if defined __linux__ && defined HAVE_SYSTEM_MIDI
|
||||
|
||||
#include "zmusic/zmusic.h"
|
||||
#include <vector>
|
||||
#include <string>
|
||||
typedef struct _snd_seq snd_seq_t;
|
||||
|
||||
// FIXME: make not visible from outside
|
||||
struct MidiOutDeviceInternal {
|
||||
std::string Name;
|
||||
int ID = -1;
|
||||
int ClientID = -1;
|
||||
int PortNumber = -1;
|
||||
unsigned int type = 0;
|
||||
};
|
||||
|
||||
// NOTE: the sequencer state is shared between actually playing MIDI music and device enumeration, therefore we keep it around.
|
||||
class AlsaSequencer {
|
||||
private:
|
||||
AlsaSequencer();
|
||||
~AlsaSequencer();
|
||||
|
||||
public:
|
||||
static AlsaSequencer &Get();
|
||||
bool Open();
|
||||
void Close();
|
||||
bool IsOpen() const {
|
||||
return nullptr != handle;
|
||||
}
|
||||
|
||||
int EnumerateDevices();
|
||||
const std::vector<MidiOutDevice> &GetDevices();
|
||||
const std::vector<MidiOutDeviceInternal> &GetInternalDevices();
|
||||
|
||||
snd_seq_t *handle = nullptr;
|
||||
|
||||
int OurId = -1;
|
||||
int error = -1;
|
||||
|
||||
private:
|
||||
std::vector<MidiOutDeviceInternal> internalDevices;
|
||||
std::vector<MidiOutDevice> externalDevices;
|
||||
};
|
||||
|
||||
#endif
|
|
@ -41,6 +41,12 @@
|
|||
#include "mididevices/mididevice.h"
|
||||
#include "midisources/midisource.h"
|
||||
|
||||
#ifdef HAVE_SYSTEM_MIDI
|
||||
#ifdef __linux__
|
||||
#include "mididevices/music_alsa_state.h"
|
||||
#endif
|
||||
#endif
|
||||
|
||||
// MACROS ------------------------------------------------------------------
|
||||
|
||||
enum
|
||||
|
@ -226,8 +232,8 @@ EMidiDevice MIDIStreamer::SelectMIDIDevice(EMidiDevice device)
|
|||
case -7: return MDEV_ADL;
|
||||
case -8: return MDEV_OPN;
|
||||
default:
|
||||
#ifdef _WIN32
|
||||
return MDEV_MMAPI;
|
||||
#ifdef HAVE_SYSTEM_MIDI
|
||||
return MDEV_STANDARD;
|
||||
#else
|
||||
return MDEV_SNDSYS;
|
||||
#endif
|
||||
|
@ -268,13 +274,17 @@ MIDIDevice *MIDIStreamer::CreateMIDIDevice(EMidiDevice devtype, int samplerate)
|
|||
dev = CreateOPNMIDIDevice(Args.c_str());
|
||||
break;
|
||||
|
||||
case MDEV_MMAPI:
|
||||
case MDEV_STANDARD:
|
||||
|
||||
#ifdef HAVE_SYSTEM_MIDI
|
||||
#ifdef _WIN32
|
||||
dev = CreateWinMIDIDevice(std::max(0, miscConfig.snd_mididevice));
|
||||
#elif __linux__
|
||||
dev = CreateAlsaMIDIDevice(std::max(0, miscConfig.snd_mididevice));
|
||||
#endif
|
||||
break;
|
||||
#endif
|
||||
// Intentional fall-through for non-Windows systems.
|
||||
// Intentional fall-through for systems without standard midi support
|
||||
|
||||
#ifdef HAVE_FLUIDSYNTH
|
||||
case MDEV_FLUIDSYNTH:
|
||||
|
@ -308,8 +318,8 @@ MIDIDevice *MIDIStreamer::CreateMIDIDevice(EMidiDevice devtype, int samplerate)
|
|||
else if (!checked[MDEV_TIMIDITY]) devtype = MDEV_TIMIDITY;
|
||||
else if (!checked[MDEV_WILDMIDI]) devtype = MDEV_WILDMIDI;
|
||||
else if (!checked[MDEV_GUS]) devtype = MDEV_GUS;
|
||||
#ifdef _WIN32
|
||||
else if (!checked[MDEV_MMAPI]) devtype = MDEV_MMAPI;
|
||||
#ifdef HAVE_SYSTEM_MIDI
|
||||
else if (!checked[MDEV_STANDARD]) devtype = MDEV_STANDARD;
|
||||
#endif
|
||||
else if (!checked[MDEV_OPL]) devtype = MDEV_OPL;
|
||||
|
||||
|
@ -374,9 +384,9 @@ bool MIDIStreamer::DumpWave(const char *filename, int subsong, int samplerate)
|
|||
|
||||
assert(MIDI == NULL);
|
||||
auto devtype = SelectMIDIDevice(DeviceType);
|
||||
if (devtype == MDEV_MMAPI)
|
||||
if (devtype == MDEV_STANDARD)
|
||||
{
|
||||
throw std::runtime_error("MMAPI device is not supported");
|
||||
throw std::runtime_error("System MIDI device is not supported");
|
||||
}
|
||||
auto iMIDI = CreateMIDIDevice(devtype, samplerate);
|
||||
auto writer = new MIDIWaveWriter(filename, static_cast<SoftSynthMIDIDevice*>(iMIDI));
|
||||
|
|
|
@ -40,6 +40,7 @@
|
|||
#include "zmusic.h"
|
||||
#include "musinfo.h"
|
||||
#include "midiconfig.h"
|
||||
#include "mididevices/music_alsa_state.h"
|
||||
|
||||
struct Dummy
|
||||
{
|
||||
|
@ -78,6 +79,38 @@ void ZMusic_SetDmxGus(const void* data, unsigned len)
|
|||
memcpy(gusConfig.dmxgus.data(), data, len);
|
||||
}
|
||||
|
||||
int ZMusic_EnumerateMidiDevices()
|
||||
{
|
||||
#ifdef HAVE_SYSTEM_MIDI
|
||||
#ifdef __linux__
|
||||
auto & sequencer = AlsaSequencer::Get();
|
||||
return sequencer.EnumerateDevices();
|
||||
#elif _WIN32
|
||||
// TODO: move the weird stuff from music_midi_base.cpp here, or at least to this lib and call it here
|
||||
return {};
|
||||
#endif
|
||||
#else
|
||||
return {};
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
const std::vector<MidiOutDevice> &ZMusic_GetMidiDevices()
|
||||
{
|
||||
#ifdef HAVE_SYSTEM_MIDI
|
||||
#ifdef __linux__
|
||||
auto & sequencer = AlsaSequencer::Get();
|
||||
return sequencer.GetDevices();
|
||||
#elif _WIN32
|
||||
// TODO: move the weird stuff from music_midi_base.cpp here, or at least to this lib and call it here
|
||||
return {};
|
||||
#endif
|
||||
#else
|
||||
return {};
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
template<class valtype>
|
||||
void ChangeAndReturn(valtype &variable, valtype value, valtype *realv)
|
||||
{
|
||||
|
|
|
@ -43,7 +43,7 @@ enum EMidiEvent : uint8_t
|
|||
enum EMidiDevice
|
||||
{
|
||||
MDEV_DEFAULT = -1,
|
||||
MDEV_MMAPI = 0,
|
||||
MDEV_STANDARD = 0,
|
||||
MDEV_OPL = 1,
|
||||
MDEV_SNDSYS = 2,
|
||||
MDEV_TIMIDITY = 3,
|
||||
|
|
|
@ -211,9 +211,9 @@ MusInfo *ZMusic_OpenSong (MusicIO::FileInterface *reader, EMidiDevice device, co
|
|||
return nullptr;
|
||||
}
|
||||
|
||||
#ifndef _WIN32
|
||||
// non-Windows platforms don't support MDEV_MMAPI so map to MDEV_SNDSYS
|
||||
if (device == MDEV_MMAPI)
|
||||
#ifndef HAVE_SYSTEM_MIDI
|
||||
// some platforms don't support MDEV_STANDARD so map to MDEV_SNDSYS
|
||||
if (device == MDEV_STANDARD)
|
||||
device = MDEV_SNDSYS;
|
||||
#endif
|
||||
|
||||
|
|
|
@ -2,6 +2,8 @@
|
|||
|
||||
#include "mididefs.h"
|
||||
#include "../../music_common/fileio.h"
|
||||
#include <vector>
|
||||
#include <string>
|
||||
|
||||
namespace ZMusic // Namespaced because these conflict with the same-named CVARs
|
||||
{
|
||||
|
@ -149,6 +151,13 @@ void ZMusic_SetWgOpn(const void* data, unsigned len);
|
|||
// Set DMXGUS data for running the GUS synth in actual GUS mode.
|
||||
void ZMusic_SetDmxGus(const void* data, unsigned len);
|
||||
|
||||
struct MidiOutDevice {
|
||||
std::string Name;
|
||||
int ID = -1;
|
||||
};
|
||||
int ZMusic_EnumerateMidiDevices();
|
||||
const std::vector<MidiOutDevice> &ZMusic_GetMidiDevices();
|
||||
|
||||
// These exports are needed by the MIDI dumpers which need to remain on the client side.
|
||||
class MIDISource; // abstract for the client
|
||||
class MusInfo;
|
||||
|
|
|
@ -270,9 +270,12 @@ void I_InitMusic (void)
|
|||
|
||||
nomusic = !!Args->CheckParm("-nomusic") || !!Args->CheckParm("-nosound");
|
||||
|
||||
// TODO: remove, move functionality to ZMusic_EnumerateMidiDevices
|
||||
#ifdef _WIN32
|
||||
I_InitMusicWin32 ();
|
||||
#endif // _WIN32
|
||||
|
||||
ZMusic_EnumerateMidiDevices();
|
||||
snd_mididevice.Callback();
|
||||
|
||||
Callbacks callbacks;
|
||||
|
|
|
@ -167,12 +167,20 @@ CCMD (snd_listmididevices)
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
#else
|
||||
#else // _WIN32
|
||||
|
||||
void I_BuildMIDIMenuList (FOptionValues *opt)
|
||||
{
|
||||
AddDefaultMidiDevices(opt);
|
||||
|
||||
auto devices = ZMusic_GetMidiDevices();
|
||||
|
||||
for (auto & device: devices)
|
||||
{
|
||||
FOptionValues::Pair *pair = &opt->mValues[opt->mValues.Reserve(1)];
|
||||
pair->Text = device.Name.c_str();
|
||||
pair->Value = (float)device.ID;
|
||||
}
|
||||
}
|
||||
|
||||
CCMD (snd_listmididevices)
|
||||
|
@ -184,13 +192,21 @@ CCMD (snd_listmididevices)
|
|||
Printf("%s-4. Gravis Ultrasound Emulation\n", -4 == snd_mididevice ? TEXTCOLOR_BOLD : "");
|
||||
Printf("%s-3. Emulated OPL FM Synth\n", -3 == snd_mididevice ? TEXTCOLOR_BOLD : "");
|
||||
Printf("%s-2. TiMidity++\n", -2 == snd_mididevice ? TEXTCOLOR_BOLD : "");
|
||||
|
||||
auto devices = ZMusic_GetMidiDevices();
|
||||
|
||||
for (auto & device: devices)
|
||||
{
|
||||
Printf("%s%d. %s\n", -2 == snd_mididevice ? TEXTCOLOR_BOLD : "", device.ID, device.Name.c_str());
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
CUSTOM_CVAR (Int, snd_mididevice, DEF_MIDIDEV, CVAR_ARCHIVE|CVAR_GLOBALCONFIG|CVAR_NOINITCALL)
|
||||
{
|
||||
if ((self >= (signed)nummididevices) || (self < -8))
|
||||
auto devices = ZMusic_GetMidiDevices();
|
||||
if ((self >= (signed)devices.size()) || (self < -8))
|
||||
{
|
||||
// Don't do repeated message spam if there is no valid device.
|
||||
if (self != 0)
|
||||
|
|
|
@ -1230,7 +1230,7 @@ static void S_AddSNDINFO (int lump)
|
|||
MidiDeviceSetting devset;
|
||||
if (sc.Compare("timidity")) devset.device = MDEV_TIMIDITY;
|
||||
else if (sc.Compare("fmod") || sc.Compare("sndsys")) devset.device = MDEV_SNDSYS;
|
||||
else if (sc.Compare("standard")) devset.device = MDEV_MMAPI;
|
||||
else if (sc.Compare("standard")) devset.device = MDEV_STANDARD;
|
||||
else if (sc.Compare("opl")) devset.device = MDEV_OPL;
|
||||
else if (sc.Compare("default")) devset.device = MDEV_DEFAULT;
|
||||
else if (sc.Compare("fluidsynth")) devset.device = MDEV_FLUIDSYNTH;
|
||||
|
|
Loading…
Reference in a new issue