| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370 | /* -*- mode: c; tab-width: 4; c-basic-offset: 4; c-file-style: "linux" -*- *///// Copyright (c) 2009-2011, Wei Mingzhi <whistler_wmz@users.sf.net>.// Copyright (c) 2011-2017, SDLPAL development team.// All rights reserved.//// This file is part of SDLPAL.//// SDLPAL 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// (at your option) 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/>.//// native_midi.cpp: Native Windows Desktop MIDI player for SDLPal.//         @Author: Lou Yihua, 2017//#include "SDL.h"#define WIN32_LEAN_AND_MEAN#include <windows.h>#include <mmsystem.h>#include <stdlib.h>#include <memory>#include <future>#include <thread>#include <condition_variable>#include <mutex>#include <atomic>#include <vector>#if (defined(__MINGW32__) || defined(__MINGW64__)) && !defined(_GLIBCXX_HAS_GTHREADS)#include "mingw.condition_variable.h"#include "mingw.mutex.h"#include "mingw.thread.h"#endif#include "native_midi/native_midi.h"#include "native_midi/native_midi_common.h"static int native_midi_available = -1;enum class MidiSystemMessage {	Exclusive = 0,	TimeCode = 1,	SongPositionPointer = 2,	SongSelect = 3,	TuneRequest = 6,	EndOfExclusive = 7,	TimingClock = 8,	Start = 10,	Continue = 11,	Stop = 12,	ActiveSensing = 14,	SystemReset = 15};struct MidiMessage{	virtual MMRESULT Send(HMIDIOUT hmo) = 0;};struct MidiShortMessage	: public MidiMessage{	uint32_t data;	MidiShortMessage(uint32_t data) : data(data) {}	virtual MMRESULT Send(HMIDIOUT hmo) { return midiOutShortMsg(hmo, data); }};struct MidiLongMessage	: public MidiMessage{	MIDIHDR hdr;	MidiLongMessage(uint8_t* data, int length)	{		memset(&hdr, 0, sizeof(MIDIHDR));		hdr.lpData = (LPSTR)malloc(length);		hdr.dwBufferLength = hdr.dwBytesRecorded = length;		memcpy(hdr.lpData, data, length);	}	virtual MMRESULT Send(HMIDIOUT hmo)	{		MMRESULT retval;		if (MMSYSERR_NOERROR == (retval = midiOutPrepareHeader(hmo, &hdr, sizeof(MIDIHDR))))		{			retval = midiOutLongMsg(hmo, &hdr, sizeof(MIDIHDR));			midiOutUnprepareHeader(hmo, &hdr, sizeof(MIDIHDR));		}		return retval;	}};struct MidiResetMessage	: public MidiMessage{	virtual MMRESULT Send(HMIDIOUT hmo) { return midiOutReset(hmo); }};struct MidiCustomMessage	: public MidiMessage{	uint32_t message;	uint32_t data1;	uint32_t data2;	MidiCustomMessage(uint8_t status, uint8_t data1, uint8_t data2)		: message(status), data1(data1), data2(data2)	{}	virtual MMRESULT SendEvent(HMIDIOUT hmo) { return midiOutMessage(hmo, message, data1, data2); }};struct MidiEvent{	std::unique_ptr<MidiMessage> message;	uint32_t                     deltaTime;		// time in ticks	uint32_t                     tempo;			// microseconds per quarter note	std::chrono::system_clock::duration DeltaTimeAsTick(uint16_t ppq)	{		return std::chrono::microseconds((int64_t)deltaTime * tempo / ppq);	}	MMRESULT Send(HMIDIOUT hmo) { return message->Send(hmo); }};struct _NativeMidiSong {	std::vector<MidiEvent>  Events;	std::thread             Thread;	std::mutex              Mutex;	std::condition_variable Stop;	HMIDIOUT                Synthesizer;	int                     Size;	int                     Position;	Uint16                  ppq;		// parts (ticks) per quarter note	volatile bool           Playing;	bool                    Loaded;	bool                    Looping;	_NativeMidiSong()		: Synthesizer(nullptr), Size(0), Position(0)		, ppq(0), Playing(false), Loaded(false), Looping(false)	{ }};static void MIDItoStream(NativeMidiSong *song, MIDIEvent *eventlist){	int eventcount = 0, prevtime = 0, tempo = 500000;	for (MIDIEvent* event = eventlist; event; event = event->next)	{		if (event->status != 0xFF)			eventcount++;	}	song->Events.resize(song->Size = eventcount);	song->Position = 0;	song->Loaded = true;	eventcount = 0;	for (MIDIEvent* event = eventlist; event; event = event->next)	{		MidiMessage* message = nullptr;		int status = (event->status & 0xF0) >> 4;		switch (status)		{		case MIDI_STATUS_NOTE_OFF:		case MIDI_STATUS_NOTE_ON:		case MIDI_STATUS_AFTERTOUCH:		case MIDI_STATUS_CONTROLLER:		case MIDI_STATUS_PROG_CHANGE:		case MIDI_STATUS_PRESSURE:		case MIDI_STATUS_PITCH_WHEEL:			message = new MidiShortMessage(event->status | (event->data[0] << 8) | (event->data[1] << 16));			break;		case MIDI_STATUS_SYSEX:			switch ((MidiSystemMessage)(event->status & 0xF))			{			case MidiSystemMessage::Exclusive:				message = new MidiLongMessage(event->extraData, event->extraLen);				break;			case MidiSystemMessage::TimeCode:			case MidiSystemMessage::SongSelect:				message = new MidiShortMessage(event->status | (event->extraData[0] << 8));				break;			case MidiSystemMessage::SongPositionPointer:				message = new MidiShortMessage(event->status | (event->extraData[0] << 8) | (event->extraData[1] << 16));				break;			case MidiSystemMessage::TuneRequest:			case MidiSystemMessage::TimingClock:			case MidiSystemMessage::Start:			case MidiSystemMessage::Continue:			case MidiSystemMessage::Stop:			case MidiSystemMessage::ActiveSensing:				message = new MidiShortMessage(event->status);				break;			case MidiSystemMessage::SystemReset:				// This message is only used as meta-event in MIDI files				if (event->data[0] == 0x51)					tempo = (event->extraData[0] << 16) | (event->extraData[1] << 8) | event->extraData[2];				break;			default:				break;			}			break;		}		if (message)		{			auto evt = &song->Events[eventcount++];			evt->message.reset(message);			evt->deltaTime = event->time - prevtime;			evt->tempo = tempo;			prevtime = event->time;		}	}}int native_midi_detect(){	if (-1 == native_midi_available)	{		HMIDIOUT out;		if (MMSYSERR_NOERROR == midiOutOpen(&out, MIDI_MAPPER, 0, 0, CALLBACK_NULL))		{			midiOutClose(out);			native_midi_available = 1;		}		else			native_midi_available = 0;	}	return native_midi_available;}NativeMidiSong *native_midi_loadsong(const char *midifile){	/* Attempt to load the midi file */	auto rw = SDL_RWFromFile(midifile, "rb");	if (rw)	{		auto song = native_midi_loadsong_RW(rw);		SDL_RWclose(rw);		return song;	}	return nullptr;}NativeMidiSong *native_midi_loadsong_RW(SDL_RWops *rw){	std::unique_ptr<NativeMidiSong> newsong(new NativeMidiSong);	if (newsong)	{		auto eventlist = CreateMIDIEventList(rw, &newsong->ppq);		if (eventlist)		{			MIDItoStream(newsong.get(), eventlist);			FreeMIDIEventList(eventlist);						if (midiOutOpen(&newsong->Synthesizer, MIDI_MAPPER, NULL, 0, CALLBACK_NULL) == MMSYSERR_NOERROR)				return newsong.release();		}	}	return nullptr;}void native_midi_freesong(NativeMidiSong *song){	if (song)	{		native_midi_stop(song);		if (song->Synthesizer)			midiOutClose(song->Synthesizer);		delete song;	}}void native_midi_start(NativeMidiSong *song, int looping){	if (!song) return;	native_midi_stop(song);	song->Playing = true;	song->Looping = looping ? true : false;	song->Thread = std::move(std::thread([](NativeMidiSong *song)->void {		auto time = std::chrono::system_clock::now();		while (song->Playing)		{			do			{				song->Events[song->Position++].Send(song->Synthesizer);			} while (song->Position < song->Size && song->Events[song->Position].deltaTime == 0);			if (song->Position < song->Size)			{				auto mutex = std::unique_lock<std::mutex>(song->Mutex);				time += std::chrono::system_clock::duration(song->Events[song->Position].DeltaTimeAsTick(song->ppq));				while (song->Playing)				{					if (song->Stop.wait_until(mutex, time) == std::cv_status::timeout)						break;				}			}			else if (song->Playing = song->Looping)			{				song->Position = 0;				midiOutReset(song->Synthesizer);			}		}	}, song));}void native_midi_stop(NativeMidiSong *song){	if (song)	{		song->Playing = false;		song->Stop.notify_all();		if (song->Thread.joinable())			song->Thread.join();		song->Thread = std::move(std::thread());		if (song->Synthesizer)			midiOutReset(song->Synthesizer);	}}int native_midi_active(NativeMidiSong *song){	return (song && song->Playing) ? 1 : 0;}void native_midi_setvolume(NativeMidiSong *song, int volume){	if (song && song->Synthesizer)	{		uint16_t calcVolume;		if (volume > 127)			volume = 127;		if (volume < 0)			volume = 0;		calcVolume = volume << 9;		midiOutSetVolume(song->Synthesizer, MAKELONG(calcVolume, calcVolume));	}}const char *native_midi_error(NativeMidiSong *song){  return "";}
 |