diff options
author | Wohlstand <admin@wohlnet.ru> | 2017-10-24 23:01:01 +0300 |
---|---|---|
committer | Wohlstand <admin@wohlnet.ru> | 2017-10-24 23:01:01 +0300 |
commit | 6783770a8533dd099b39c51a5c3f7f5f4000b4bc (patch) | |
tree | 5532ae111fe681d4d410f4f3caa71500dea3b1b4 | |
parent | 306ecba785a64fa111c1198b9b986ac3e98db619 (diff) | |
download | libADLMIDI-6783770a8533dd099b39c51a5c3f7f5f4000b4bc.tar.gz libADLMIDI-6783770a8533dd099b39c51a5c3f7f5f4000b4bc.tar.bz2 libADLMIDI-6783770a8533dd099b39c51a5c3f7f5f4000b4bc.zip |
Added working seekability!
Testing and stabilizing is needed
-rw-r--r-- | include/adlmidi.h | 11 | ||||
-rw-r--r-- | libADLMIDI-test.pro | 6 | ||||
-rw-r--r-- | src/adlmidi.cpp | 31 | ||||
-rw-r--r-- | src/adlmidi_midiplay.cpp | 124 | ||||
-rw-r--r-- | src/adlmidi_private.hpp | 23 | ||||
-rw-r--r-- | utils/midiplay/adlmidiplay.cpp | 35 |
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 |