362#include "portaudio.h"
367#define ROUND(x) (int) ((x)+0.5)
378#if defined( __APPLE__ )
380#include <mach/mach_time.h>
383static double machSecondsConversionScaler_ = 0.0;
387 mach_timebase_info_data_t info;
388 kern_return_t err = mach_timebase_info( &info );
390 machSecondsConversionScaler_ = 1e-9 * (double) info.numer / (
double) info.denom;
395 return mach_absolute_time() * machSecondsConversionScaler_;
398#elif defined( __WXMSW__ )
400#include "profileapi.h"
401#include "sysinfoapi.h"
408 LARGE_INTEGER ticksPerSecond;
410 if( QueryPerformanceFrequency( &ticksPerSecond ) != 0 )
440 QueryPerformanceCounter( &time );
445 #if defined(WINAPI_FAMILY) && (WINAPI_FAMILY == WINAPI_FAMILY_APP)
446 return GetTickCount64() * .001;
448 return timeGetTime() * .001;
453#elif defined(HAVE_CLOCK_GETTIME)
460 clock_gettime(CLOCK_REALTIME, &tp);
461 return (PaTime)(tp.tv_sec + tp.tv_nsec * 1e-9);
471 gettimeofday( &tv, NULL );
472 return (PaTime) tv.tv_usec * 1e-6 + tv.tv_sec;
493#if defined(CLOCK_MONOTONIC_RAW)
494 clock_gettime(CLOCK_MONOTONIC_RAW, &now);
496 clock_gettime(CLOCK_REALTIME, &now);
502 static_cast<void>(usingAlsa);
508bool MIDIPlay::mMidiStreamActive =
false;
509bool MIDIPlay::mMidiOutputComplete =
true;
512 [](
const auto &playbackSchedule){
513 return std::make_unique<MIDIPlay>(playbackSchedule);
518 : mPlaybackSchedule{ schedule }
520#ifdef AUDIO_IO_GB_MIDI_WORKAROUND
526 PmError pmErr = Pm_Initialize();
528 if (pmErr != pmNoError) {
530 XO(
"There was an error initializing the midi i/o layer.\n");
531 errStr +=
XO(
"You will not be able to play midi.\n\n");
532 wxString pmErrStr =
LAT1CTOWX(Pm_GetErrorText(pmErr));
533 if (!pmErrStr.empty())
534 errStr +=
XO(
"Error: %s").Format( pmErrStr );
542 .ButtonStyle(Button::Ok)
543 .IconStyle(Icon::Error));
555 const PaStreamInfo* info,
double,
double rate)
558 for (
const auto &pSequence :
tracks.otherPlayableSequences)
559 if (
const auto pNoteTrack =
560 dynamic_cast<const NoteTrack *
>(pSequence.get())
563 pNoteTrack->SharedPointer<
const NoteTrack>());
593 bool successMidi =
true;
627 double startTime,
double offset,
bool send )
628 : mPlaybackSchedule{ schedule }
629 , mMIDIPlay{ midiPlay }
634 for (
auto &t : midiPlaybackTracks) {
635 Alg_seq_ptr seq = &t->GetSeq();
639 seq->set_in_use(
true);
640 const void *cookie = t.get();
643 const_cast<void*
>(cookie), t->GetStartTime() + offset);
645 Prime(send, startTime + offset);
698 PmDeviceID playbackDevice = Pm_GetDefaultOutputDeviceID();
701 if (wxStrcmp(playbackDeviceName,
wxT(
"")) != 0) {
702 for (i = 0; i < Pm_CountDevices(); i++) {
703 const PmDeviceInfo *info = Pm_GetDeviceInfo(i);
705 if (!info->output)
continue;
706 wxString interf = wxSafeConvertMB2WX(info->interf);
707 wxString
name = wxSafeConvertMB2WX(info->name);
708 interf.Append(
wxT(
": ")).Append(
name);
709 if (wxStrcmp(interf, playbackDeviceName) == 0) {
715 if (playbackDevice < 0)
762 using namespace std::chrono;
763 std::this_thread::sleep_for(1ms);
771 for (
int i = 0; i < nTracks; i++) {
773 Alg_seq_ptr seq = &t->GetSeq();
774 seq->set_in_use(
false);
793 return time + pauseTime;
801 const bool channelIsMute = hasSolo
804 return !channelIsMute;
817 double time = eventTime + 0.0005 -
824 if (time < 0 || midiStateOnly)
867 const bool sendIt = [&]{
879 if (
mNextEvent->is_note() && !midiStateOnly) {
887 data2 = (data2 < 1 ? 1 : (data2 > 127 ? 127 : data2));
889 it.request_note_off();
891#ifdef AUDIO_IO_GB_MIDI_WORKAROUND
897#ifdef AUDIO_IO_GB_MIDI_WORKAROUND
899 auto iter = std::find(
910 Alg_update_ptr update =
static_cast<Alg_update_ptr
>(
mNextEvent);
911 const char *
name = update->get_attribute();
913 if (!strcmp(
name,
"programi")) {
915 data1 = update->parameter.i;
918 }
else if (!strncmp(
name,
"control", 7)) {
923 data1 = atoi(
name + 7);
925 data2 =
ROUND(update->parameter.r * 127);
927 }
else if (!strcmp(
name,
"bendr")) {
931 int temp =
ROUND(0x2000 * (update->parameter.r + 1));
932 if (temp > 0x3fff) temp = 0x3fff;
933 if (temp < 0) temp = 0;
937 }
else if (!strcmp(
name,
"pressurer")) {
939 data1 = (int) (update->parameter.r * 127);
940 if (update->get_identifier() < 0) {
947 data1 = update->get_identifier();
958 Pm_Message((
int) (command + channel),
959 (
long) data1, (
long) data2));
993 double rate,
unsigned long pauseFrames,
bool paused,
bool hasSolo)
1032 return pauseFrames / rate;
1056 (1000 * (now + 1.0005 -
1067 bool doDelay = !looping;
1069 bool doDelay =
false;
1070 static_cast<void>(looping);
1079#ifdef AUDIO_IO_GB_MIDI_WORKAROUND
1101 0x90 + pair.first, pair.second, 0));
1109 for (
int chan = 0; chan < 16; chan++) {
1112 Pm_Message(0xB0 + chan, 0x7B, 0));
1118 const PaStreamCallbackTimeInfo *timeInfo,
1119 unsigned long framesPerBuffer
1146 const auto increase =
1176 (timeInfo->outputBufferDacTime - timeInfo->currentTime);
1195 return std::count_if(
1197 [](
const auto &pTrack){ return pTrack->GetSolo(); } );
1206bool MIDIPlay::IsActive()
1208 return ( mMidiStreamActive && !mMidiOutputComplete );
1211bool MIDIPlay::IsOtherStreamActive()
const
1221 wxT(
"MIDI Device Info")
StringSetting AudioIOHost
Toolkit-neutral facade for basic user interface services.
const TranslatableString name
std::vector< std::shared_ptr< const NoteTrack > > NoteTrackConstArray
Inject added MIDI playback capability into Audacity's audio engine.
IntSetting MIDISynthLatency_ms
wxString GetMIDIDeviceInfo()
StringSetting MIDIPlaybackDevice
Callbacks that AudioIO uses, to synchronize audio and MIDI playback.
A Track that is used for Midi notes. (Somewhat old code).
bool IsVisibleChan(int c) const
virtual bool Looping(const PlaybackSchedule &schedule) const
bool Read(T *pVar) const
overload of Read returning a boolean that is true if the value was previously defined */
MessageBoxResult ShowMessageBox(const TranslatableString &message, MessageBoxOptions options={})
Show a modal message box with either Ok or Yes and No, and optionally Cancel.
auto end(const Ptr< Type, BaseDeleter > &p)
Enables range-for.
static double util_GetTime(void)
static double secondsPerTick_
static double SystemTime(bool usingAlsa)
static struct anonymous_namespace{MIDIPlay.cpp}::InitializeTime initializeTime
static double streamStartTime
PmTimestamp MidiTime(void *pInfo)
static int usePerformanceCounter_
AudioIOExt::RegisteredFactory sMIDIPlayFactory
@ MIDI_MINIMAL_LATENCY_MS
Typically statically constructed.
MessageBoxOptions && Caption(TranslatableString caption_) &&
double mT0
Playback starts at offset of mT0, which is measured in seconds.
double mT1
Playback ends at offset of mT1, which is measured in seconds. Note that mT1 may be less than mT0 duri...
const BoundedEnvelope * mEnvelope
double RealDuration(double trackTime1) const
PlaybackPolicy & GetPolicy()
bool Unmuted(bool hasSolo) const
Iterator(const PlaybackSchedule &schedule, MIDIPlay &midiPlay, NoteTrackConstArray &midiPlaybackTracks, double startTime, double offset, bool send)
NoteTrack * mNextEventTrack
Track of next event.
const PlaybackSchedule & mPlaybackSchedule
double GetNextEventTime() const
double UncorrectedMidiEventTime(double pauseTime)
bool mNextIsNoteOn
Is the next event a note-on?
bool OutputEvent(double pauseTime, bool sendMidiState, bool hasSolo)
void Prime(bool send, double startTime)
Alg_event * mNextEvent
The next event to play (or null)
double mSystemMinusAudioTime
bool StartOtherStream(const TransportSequences &tracks, const PaStreamInfo *info, double startTime, double rate) override
PmTimestamp MidiTime()
Compute the current PortMidi timestamp time.
void SignalOtherCompletion() override
void AbortOtherStream() override
PmTimestamp mMaxMidiTimestamp
std::optional< Iterator > mIterator
std::vector< std::pair< int, int > > mPendingNotesOff
NoteTrackConstArray mMidiPlaybackTracks
void StopOtherStream() override
void ComputeOtherTimings(double rate, bool paused, const PaStreamCallbackTimeInfo *timeInfo, unsigned long framesPerBuffer) override
static bool mMidiStreamActive
mMidiStreamActive tells when mMidiStream is open for output
const PlaybackSchedule & mPlaybackSchedule
long mSynthLatency
Latency of MIDI synthesizer.
void PrepareMidiIterator(bool send, double startTime, double offset)
long mCallbackCount
number of callbacks since stream start
bool StartPortMidiStream(double rate)
double mSystemMinusAudioTimePlusLatency
static bool mMidiOutputComplete
True when output reaches mT1.
int mMidiLoopPasses
total of backward jumps
long mAudioFramesPerBuffer
double AudioTime(double rate) const
void AllNotesOff(bool looping=false)
long mNumFrames
Number of frames output, including pauses.
double PauseTime(double rate, unsigned long pauseFrames)
void FillOtherBuffers(double rate, unsigned long pauseFrames, bool paused, bool hasSolo) override
unsigned CountOtherSolo() const override