aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorWohlstand <admin@wohlnet.ru>2017-10-24 23:01:01 +0300
committerWohlstand <admin@wohlnet.ru>2017-10-24 23:01:01 +0300
commit6783770a8533dd099b39c51a5c3f7f5f4000b4bc (patch)
tree5532ae111fe681d4d410f4f3caa71500dea3b1b4
parent306ecba785a64fa111c1198b9b986ac3e98db619 (diff)
downloadlibADLMIDI-6783770a8533dd099b39c51a5c3f7f5f4000b4bc.tar.gz
libADLMIDI-6783770a8533dd099b39c51a5c3f7f5f4000b4bc.tar.bz2
libADLMIDI-6783770a8533dd099b39c51a5c3f7f5f4000b4bc.zip
Added working seekability!
Testing and stabilizing is needed
-rw-r--r--include/adlmidi.h11
-rw-r--r--libADLMIDI-test.pro6
-rw-r--r--src/adlmidi.cpp31
-rw-r--r--src/adlmidi_midiplay.cpp124
-rw-r--r--src/adlmidi_private.hpp23
-rw-r--r--utils/midiplay/adlmidiplay.cpp35
6 files changed, 191 insertions, 39 deletions
diff --git a/include/adlmidi.h b/include/adlmidi.h
index 145b5dd..e2aaf59 100644
--- a/include/adlmidi.h
+++ b/include/adlmidi.h
@@ -127,6 +127,15 @@ extern int adl_openData(struct ADL_MIDIPlayer *device, void *mem, long size);
/*Resets MIDI player*/
extern void adl_reset(struct ADL_MIDIPlayer *device);
+/*Get total time length of current song*/
+extern double adl_totalTimeLength(struct ADL_MIDIPlayer *device);
+
+/*Get current time position in seconds*/
+extern double adl_positionTell(struct ADL_MIDIPlayer *device);
+
+/*Jump to absolute time position in seconds*/
+extern void adl_positionSeek(struct ADL_MIDIPlayer *device, double seconds);
+
/*Reset MIDI track position to begin */
extern void adl_positionRewind(struct ADL_MIDIPlayer *device);
@@ -134,7 +143,7 @@ extern void adl_positionRewind(struct ADL_MIDIPlayer *device);
extern void adl_close(struct ADL_MIDIPlayer *device);
/*Take a sample buffer*/
-extern int adl_play(struct ADL_MIDIPlayer *device, int sampleCount, short out[]);
+extern int adl_play(struct ADL_MIDIPlayer *device, int sampleCount, short out[]);
#ifdef __cplusplus
}
diff --git a/libADLMIDI-test.pro b/libADLMIDI-test.pro
index 66432f4..03ac954 100644
--- a/libADLMIDI-test.pro
+++ b/libADLMIDI-test.pro
@@ -14,13 +14,15 @@ INCLUDEPATH += $$PWD/src $$PWD/include
LIBS += -lSDL2 -lpthread -ldl
#DEFINES += DEBUG_TIME_CALCULATION
-DEFINES += DEBUG_SHOW_AUDIO_TIMER
+#DEFINES += DEBUG_SEEKING_TEST
QMAKE_CFLAGS += -std=c90 -pedantic
QMAKE_CXXFLAGS += -std=c++98 -pedantic
#DEFINES += DISABLE_EMBEDDED_BANKS
+#DEFINES += ADLMIDI_USE_DOSBOX_OPL
+
HEADERS += \
include/adlmidi.h \
src/adlbank.h \
@@ -30,6 +32,7 @@ HEADERS += \
src/adlmidi_xmi2mid.h \
src/fraction.h \
src/nukedopl3.h \
+ #src/dbopl.h \
src/midiplay/wave_writer.h
SOURCES += \
@@ -43,6 +46,7 @@ SOURCES += \
src/adlmidi_private.cpp \
src/adlmidi_xmi2mid.c \
src/nukedopl3.c \
+ #src/dbopl.cpp \
utils/midiplay/adlmidiplay.cpp \
utils/midiplay/wave_writer.c
diff --git a/src/adlmidi.cpp b/src/adlmidi.cpp
index 7f8564b..1bde95a 100644
--- a/src/adlmidi.cpp
+++ b/src/adlmidi.cpp
@@ -319,6 +319,27 @@ ADLMIDI_EXPORT void adl_reset(ADL_MIDIPlayer *device)
reinterpret_cast<MIDIplay *>(device->adl_midiPlayer)->opl.Reset();
}
+ADLMIDI_EXPORT double adl_totalTimeLength(ADL_MIDIPlayer *device)
+{
+ if(!device)
+ return -1.0;
+ return reinterpret_cast<MIDIplay *>(device->adl_midiPlayer)->timeLength();
+}
+
+ADLMIDI_EXPORT double adl_positionTell(struct ADL_MIDIPlayer *device)
+{
+ if(!device)
+ return -1.0;
+ return reinterpret_cast<MIDIplay *>(device->adl_midiPlayer)->tell();
+}
+
+ADLMIDI_EXPORT void adl_positionSeek(struct ADL_MIDIPlayer *device, double seconds)
+{
+ if(!device)
+ return;
+ reinterpret_cast<MIDIplay *>(device->adl_midiPlayer)->seek(seconds);
+}
+
ADLMIDI_EXPORT void adl_positionRewind(struct ADL_MIDIPlayer *device)
{
if(!device)
@@ -327,6 +348,7 @@ ADLMIDI_EXPORT void adl_positionRewind(struct ADL_MIDIPlayer *device)
}
+
#ifdef ADLMIDI_USE_DOSBOX_OPL
#define ADLMIDI_CLAMP(V, MIN, MAX) std::max(std::min(V, (MAX)), (MIN))
@@ -379,13 +401,13 @@ inline static void SendStereoAudio(ADL_MIDIPlayer *device,
size_t inSamples = static_cast<size_t>(in_size * 2);
size_t maxSamples = static_cast<size_t>(samples_requested) - offset;
size_t toCopy = std::min(maxSamples, inSamples);
- memcpy(_out + out_pos, _in, toCopy * sizeof(short));
+ std::memcpy(_out + out_pos, _in, toCopy * sizeof(short));
if(maxSamples < inSamples)
{
size_t appendSize = inSamples - maxSamples;
- memcpy(device->backup_samples + device->backup_samples_size,
- maxSamples + _in, appendSize * sizeof(short));
+ std::memcpy(device->backup_samples + device->backup_samples_size,
+ maxSamples + _in, appendSize * sizeof(short));
device->backup_samples_size += (ssize_t)appendSize;
device->stored_samples += (ssize_t)appendSize;
}
@@ -480,7 +502,7 @@ ADLMIDI_EXPORT int adl_play(ADL_MIDIPlayer *device, int sampleCount, short *out)
in_mixBuffer.resize(1024); //n_samples * 2
ssize_t in_generatedStereo = n_periodCountStereo;
#endif
- memset(out_buf.data(), 0, in_countStereoU * sizeof(short));
+ std::memset(out_buf.data(), 0, in_countStereoU * sizeof(short));
/* Generate data from every chip and mix result */
for(unsigned card = 0; card < device->NumCards; ++card)
@@ -502,6 +524,7 @@ ADLMIDI_EXPORT int adl_play(ADL_MIDIPlayer *device, int sampleCount, short *out)
left -= (int)in_generatedPhys;
gotten_len += (in_generatedPhys) - device->stored_samples;
+ out_buf.clear();
}
device->delay = reinterpret_cast<MIDIplay *>(device->adl_midiPlayer)->Tick(eat_delay, device->mindelay);
diff --git a/src/adlmidi_midiplay.cpp b/src/adlmidi_midiplay.cpp
index d1ee447..5caa15e 100644
--- a/src/adlmidi_midiplay.cpp
+++ b/src/adlmidi_midiplay.cpp
@@ -140,8 +140,7 @@ MIDIplay::MidiTrackPos::MidiTrackPos() :
time(0.0),
delay(0),
absPos(0),
- timeDelay(0.0),
- next(NULL)
+ timeDelay(0.0)
{}
void MIDIplay::MidiTrackPos::reset()
@@ -151,7 +150,6 @@ void MIDIplay::MidiTrackPos::reset()
absPos = 0;
timeDelay = 0.0;
events.clear();
- next = NULL;
}
void MIDIplay::MidiTrackPos::sortEvents()
@@ -189,10 +187,11 @@ void MIDIplay::MidiTrackPos::sortEvents()
void MIDIplay::buildTrackData()
{
+ fullSongTimeLength = 0.0;
trackDataNew.clear();
trackDataNewStatus.clear();
const size_t trackCount = TrackData.size();
- trackDataNew.resize(trackCount, std::vector<MidiTrackPos>());
+ trackDataNew.resize(trackCount, MidiTrackQueue());
trackDataNewStatus.resize(trackCount, 0);
CurrentPositionNew.track.clear();
@@ -233,7 +232,9 @@ void MIDIplay::buildTrackData()
tempos.push_back(event);
}
- evtPos.delay = ReadVarLen(&trackPtr);
+ if(event.subtype != MidiEvent::ST_ENDTRACK)//Don't try to read delta after EndOfTrack event!
+ evtPos.delay = ReadVarLen(&trackPtr);
+
if((evtPos.delay > 0) || (event.subtype == MidiEvent::ST_ENDTRACK))
{
evtPos.absPos = abs_position;
@@ -245,11 +246,10 @@ void MIDIplay::buildTrackData()
} while(event.subtype != MidiEvent::ST_ENDTRACK);
// Build the chain of events
- for(size_t i = 0, j = 1; i < trackDataNew[tk].size() && j < trackDataNew[tk].size(); i++, j++)
- trackDataNew[tk][i].next = &(trackDataNew[tk][j]);
-
+ //for(size_t i = 0, j = 1; i < trackDataNew[tk].size() && j < trackDataNew[tk].size(); i++, j++)
+ // trackDataNew[tk][i].next = &(trackDataNew[tk][j]);
if(trackDataNew[tk].size() > 0)
- CurrentPositionNew.track[tk].pos = &trackDataNew[tk][0];
+ CurrentPositionNew.track[tk].pos = trackDataNew[tk].begin();
}
//! Calculate time basing on collected tempo events
@@ -259,7 +259,7 @@ void MIDIplay::buildTrackData()
double time = 0.0;
uint8_t abs_position = 0;
size_t tempo_change_index = 0;
- std::vector<MidiTrackPos> &track = trackDataNew[tk];
+ MidiTrackQueue &track = trackDataNew[tk];
if(track.empty())
continue;//Empty track is useless!
@@ -268,8 +268,8 @@ void MIDIplay::buildTrackData()
std::fflush(stdout);
#endif
- MidiTrackPos *posPrev = &track[0];//First element
- for(std::vector<MidiTrackPos>::iterator it = track.begin(); it != track.end(); it++)
+ MidiTrackPos *posPrev = &(*(track.begin()));//First element
+ for(MidiTrackQueue::iterator it = track.begin(); it != track.end(); it++)
{
#ifdef DEBUG_TIME_CALCULATION
bool tempoChanged = false;
@@ -353,11 +353,18 @@ void MIDIplay::buildTrackData()
abs_position += pos.delay;
posPrev = &pos;
}
+
+ if(time > fullSongTimeLength)
+ fullSongTimeLength = time;
}
+
+ fullSongTimeLength += postSongWaitDelay;
}
MIDIplay::MIDIplay():
cmf_percussion_mode(false),
+ fullSongTimeLength(0.0),
+ postSongWaitDelay(1.0),
config(NULL),
trackStart(false),
atEnd(false),
@@ -403,12 +410,6 @@ double MIDIplay::Tick(double s, double granularity)
CurrentPositionNew.wait -= s;
CurrentPositionNew.absTimePosition += s;
- #ifdef DEBUG_SHOW_AUDIO_TIMER
- std::fprintf(stdout, " \r");
- std::fprintf(stdout, "Time position: %10f\r", CurrentPositionNew.absTimePosition);
- std::fflush(stdout);
- #endif
-
int antiFreezeCounter = 10000;//Limit 10000 loops to avoid freezing
while((CurrentPositionNew.wait <= granularity * 0.5) && (antiFreezeCounter > 0))
{
@@ -429,7 +430,11 @@ double MIDIplay::Tick(double s, double granularity)
UpdateVibrato(s);
UpdateArpeggio(s);
+ if(CurrentPositionNew.wait < 0.0)//Avoid negative delay value!
+ return 0.0;
+
return CurrentPositionNew.wait;
+
// if(CurrentPosition.began)
// CurrentPosition.wait -= s;
@@ -453,12 +458,70 @@ double MIDIplay::Tick(double s, double granularity)
// UpdateVibrato(s);
// UpdateArpeggio(s);
-// return CurrentPosition.wait;
+ // return CurrentPosition.wait;
+}
+
+void MIDIplay::seek(double seconds)
+{
+ if(seconds < 0.0)
+ return;//Seeking negative position is forbidden! :-P
+ ADL_MIDIPlayer *device = opl._parent;
+ const double granularity = device->mindelay,
+ s = device->delay < device->maxdelay ? device->delay : device->maxdelay;
+
+ unsigned int loopFlagState = opl._parent->loopingIsEnabled;
+ opl._parent->loopingIsEnabled = 0;
+
+ rewind();
+ while((CurrentPositionNew.absTimePosition < seconds) &&
+ (CurrentPositionNew.absTimePosition < fullSongTimeLength))
+ {
+ CurrentPositionNew.wait -= s;
+ CurrentPositionNew.absTimePosition += s;
+
+ int antiFreezeCounter = 10000;//Limit 10000 loops to avoid freezing
+ while((CurrentPositionNew.wait <= granularity * 0.5) && (antiFreezeCounter > 0))
+ {
+ //std::fprintf(stderr, "wait = %g...\n", CurrentPosition.wait);
+ if(!ProcessEventsNew(true))
+ break;
+ if(CurrentPositionNew.wait <= 0.0)
+ antiFreezeCounter--;
+ }
+
+ if(antiFreezeCounter <= 0)
+ CurrentPositionNew.wait += 1.0;/* Add extra 1 second when over 10000 events
+ with zero delay are been detected */
+ for(uint16_t c = 0; c < opl.NumChannels; ++c)
+ ch[c].AddAge(static_cast<int64_t>(s * 1000.0));
+ }
+
+ if(CurrentPositionNew.wait < 0.0)
+ CurrentPositionNew.wait = 0.0;
+
+ device->loopingIsEnabled = loopFlagState;
+ device->delay = CurrentPositionNew.wait;
+ device->carry = 0.0;
+ //UpdateVibrato(s);
+ UpdateArpeggio(s);
+}
+
+double MIDIplay::tell()
+{
+ return CurrentPositionNew.absTimePosition;
+}
+
+double MIDIplay::timeLength()
+{
+ return fullSongTimeLength;
}
void MIDIplay::rewind()
{
+ Panic();
+ KillSustainingNotes(-1, -1);
CurrentPosition = trackBeginPosition;
+ CurrentPositionNew = trackBeginPositionNew;
trackStart = true;
atEnd = false;
loopStart = true;
@@ -1186,7 +1249,7 @@ bool MIDIplay::ProcessEvents()
return true;//Has events in queue
}
-bool MIDIplay::ProcessEventsNew()
+bool MIDIplay::ProcessEventsNew(bool isSeek)
{
if(TrackData.size() == 0)
atEnd = true;//No MIDI track data to play
@@ -1210,6 +1273,8 @@ bool MIDIplay::ProcessEventsNew()
for(size_t i = 0; i < track.pos->events.size(); i++)
{
MidiEvent &evt = track.pos->events[i];
+ if(isSeek && (evt.type == MidiEvent::T_NOTEON))
+ continue;
HandleEvent(tk, evt, track.status);
if(loopEnd)
break;//Stop event handling on catching loopEnd event!
@@ -1221,14 +1286,14 @@ bool MIDIplay::ProcessEventsNew()
#endif
// Read next event time (unless the track just ended)
- if(track.pos->next == NULL/* >= TrackData[tk].size()*/)
+ if(track.pos == trackDataNew[tk].end())
track.status = -1;
if(track.status >= 0)
+ {
track.delay += track.pos->delay;
-
- if(track.status >= 0)
- track.pos = track.pos->next;
+ track.pos++;
+ }
}
}
@@ -1301,7 +1366,7 @@ bool MIDIplay::ProcessEventsNew()
if(opl._parent->loopingIsEnabled == 0)
{
atEnd = true; //Don't handle events anymore
- CurrentPositionNew.wait += 1.0;//One second delay until stop playing
+ CurrentPositionNew.wait += postSongWaitDelay;//One second delay until stop playing
return true;//We have caugh end here!
}
CurrentPositionNew = LoopBeginPositionNew;
@@ -1950,6 +2015,15 @@ void MIDIplay::KillOrEvacuate(size_t from_channel, AdlChannel::users_t::iterator
static_cast<int32_t>(from_channel));
}
+void MIDIplay::Panic()
+{
+ for(size_t chan = 0; chan < Ch.size(); chan++)
+ {
+ for(uint8_t note = 0; note < 128; note++)
+ realTime_NoteOff(chan, note);
+ }
+}
+
void MIDIplay::KillSustainingNotes(int32_t MidCh, int32_t this_adlchn)
{
uint32_t first = 0, last = opl.NumChannels;
diff --git a/src/adlmidi_private.hpp b/src/adlmidi_private.hpp
index 75094d2..451a8e6 100644
--- a/src/adlmidi_private.hpp
+++ b/src/adlmidi_private.hpp
@@ -50,6 +50,7 @@
#endif
#include <vector>
+#include <list>
#include <string>
#include <sstream>
#include <map>
@@ -387,10 +388,10 @@ private:
* @brief Sort events in this position
*/
void sortEvents();
- //! Next event that follows current
- MidiTrackPos *next;
};
+ typedef std::list<MidiTrackPos> MidiTrackQueue;
+
// Information about each track
struct PositionNew
{
@@ -404,15 +405,20 @@ private:
uint64_t delay;
int status;
char padding2[4];
- MidiTrackPos *pos;
- TrackInfo(): ptr(0), delay(0), status(0), pos(NULL) {}
+ MidiTrackQueue::iterator pos;
+ TrackInfo(): ptr(0), delay(0), status(0) {}
};
std::vector<TrackInfo> track;
PositionNew(): began(false), wait(0.0), absTimePosition(0.0), track()
{}
} CurrentPositionNew, LoopBeginPositionNew, trackBeginPositionNew;
- std::vector<std::vector<MidiTrackPos> > trackDataNew;
+ //! Full song length in seconds
+ double fullSongTimeLength;
+ //! Delay after song playd before rejecting the output stream requests
+ double postSongWaitDelay;
+
+ std::vector<MidiTrackQueue > trackDataNew;
std::vector<int> trackDataNewStatus;
void buildTrackData();
MidiEvent parseEvent(uint8_t **ptr, int &status);
@@ -609,6 +615,10 @@ public:
*/
double Tick(double s, double granularity);
+ void seek(double seconds);
+ double tell();
+ double timeLength();
+
void rewind();
/* RealTime event triggers */
@@ -647,7 +657,7 @@ private:
unsigned props_mask,
int32_t select_adlchn = -1);
bool ProcessEvents();
- bool ProcessEventsNew();
+ bool ProcessEventsNew(bool isSeek = false);
void HandleEvent(size_t tk);
void HandleEvent(size_t tk, MidiEvent &evt, int &status);
@@ -663,6 +673,7 @@ private:
size_t from_channel,
AdlChannel::users_t::iterator j,
MIDIchannel::activenoteiterator i);
+ void Panic();
void KillSustainingNotes(int32_t MidCh = -1, int32_t this_adlchn = -1);
void SetRPN(unsigned MidCh, unsigned value, bool MSB);
//void UpdatePortamento(unsigned MidCh)
diff --git a/utils/midiplay/adlmidiplay.cpp b/utils/midiplay/adlmidiplay.cpp
index 9fb9033..45e1cf9 100644
--- a/utils/midiplay/adlmidiplay.cpp
+++ b/utils/midiplay/adlmidiplay.cpp
@@ -3,6 +3,7 @@
#include <string>
#include <cstdio>
#include <cctype>
+#include <cmath>
#include <cstdlib>
#include <cstring>
#include <deque>
@@ -276,10 +277,18 @@ int main(int argc, char **argv)
signal(SIGHUP, sighandler);
#endif
+ double total = adl_totalTimeLength(myDevice);
+
if(!recordWave)
{
SDL_PauseAudio(0);
+ #ifdef DEBUG_SEEKING_TEST
+ int delayBeforeSeek = 20;
+ std::fprintf(stdout, "DEBUG: === Random position set test is active! ===\n");
+ std::fflush(stdout);
+ #endif
+
while(!stop)
{
short buff[4096];
@@ -287,6 +296,10 @@ int main(int argc, char **argv)
if(got <= 0)
break;
+ std::fprintf(stdout, " \r");
+ std::fprintf(stdout, "Time position: %10f / %10f\r", adl_positionTell(myDevice), total);
+ std::fflush(stdout);
+
AudioBuffer_lock.Lock();
size_t pos = AudioBuffer.size();
AudioBuffer.resize(pos + got);
@@ -299,8 +312,17 @@ int main(int argc, char **argv)
{
SDL_Delay(1);
}
- }
+ #ifdef DEBUG_SEEKING_TEST
+ if(delayBeforeSeek-- <= 0)
+ {
+ delayBeforeSeek = rand() % 20;
+ double seekTo = double((rand() % int(adl_totalTimeLength(myDevice)) - delayBeforeSeek - 1 ));
+ adl_positionSeek(myDevice, seekTo);
+ }
+ #endif
+ }
+ std::fprintf(stdout, " \n\n");
SDL_CloseAudio();
}
else
@@ -319,10 +341,19 @@ int main(int argc, char **argv)
if(got <= 0)
break;
wave_write(buff, (long)got);
+
+ double complete = std::floor(100.0 * adl_positionTell(myDevice) / total);
+ std::fprintf(stdout, " \r");
+ std::fprintf(stdout, "Recording WAV... [%d%% completed]\r", (int)complete);
+ std::fflush(stdout);
}
wave_close();
+ std::fprintf(stdout, " \n\n");
- std::fprintf(stdout, "Completed!\n");
+ if(stop)
+ std::fprintf(stdout, "Interrupted! Recorded WAV is incomplete, but playable!\n");
+ else
+ std::fprintf(stdout, "Completed!\n");
std::fflush(stdout);
}
else