JackCoreMidiOutputPort.cpp 8.45 KB
Newer Older
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
/*
Copyright (C) 2011 Devin Anderson

This program 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 2 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, write to the Free Software
Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.

*/

#include <cassert>
21
22
#include <cerrno>
#include <cstring>
23
#include <new>
24
#include <stdexcept>
25
26

#include "JackCoreMidiOutputPort.h"
27
#include "JackMidiUtil.h"
Stephane Letz's avatar
Stephane Letz committed
28
#include "JackTime.h"
29
30
31
32
33
34
35
36
37

using Jack::JackCoreMidiOutputPort;

JackCoreMidiOutputPort::JackCoreMidiOutputPort(double time_ratio,
                                               size_t max_bytes,
                                               size_t max_messages):
    JackCoreMidiPort(time_ratio)
{
    read_queue = new JackMidiBufferReadQueue();
38
39
    std::auto_ptr<JackMidiBufferReadQueue> read_queue_ptr(read_queue);
    thread_queue = new JackMidiAsyncQueue(max_bytes, max_messages);
40
    std::auto_ptr<JackMidiAsyncQueue> thread_queue_ptr(thread_queue);
41
    thread = new JackThread(this);
42
    std::auto_ptr<JackThread> thread_ptr(thread);
43
    sprintf(semaphore_name, "coremidi_%p", this);
44
45
46
47
    thread_queue_semaphore = sem_open(semaphore_name, O_CREAT, 0777, 0);
    if (thread_queue_semaphore == (sem_t *) SEM_FAILED) {
        throw std::runtime_error(strerror(errno));
    }
48
    advance_schedule_time = 0;
49
    thread_ptr.release();
50
51
    thread_queue_ptr.release();
    read_queue_ptr.release();
52
53
54
55
56
}

JackCoreMidiOutputPort::~JackCoreMidiOutputPort()
{
    delete thread;
57
58
    sem_destroy(thread_queue_semaphore);
    sem_unlink(semaphore_name);
59
60
61
62
63
64
65
66
67
68
69
70
71
    delete read_queue;
    delete thread_queue;
}

bool
JackCoreMidiOutputPort::Execute()
{
    jack_midi_event_t *event = 0;
    MIDIPacketList *packet_list = (MIDIPacketList *) packet_buffer;
    for (;;) {
        MIDIPacket *packet = MIDIPacketListInit(packet_list);
        assert(packet);
        if (! event) {
72
            event = GetCoreMidiEvent(true);
73
        }
74
        jack_midi_data_t *data = event->buffer;
75
76
77
        jack_nframes_t send_frame = event->time;
        jack_time_t send_time =
            GetTimeFromFrames(send_frame) - advance_schedule_time;
78
        size_t size = event->size;
79
        MIDITimeStamp timestamp = GetTimeStampFromFrames(send_frame);
80
81
82
        packet = MIDIPacketListAdd(packet_list, PACKET_BUFFER_SIZE, packet,
                                   timestamp, size, data);
        if (packet) {
83
            while (GetMicroSeconds() < send_time) {
84
                event = GetCoreMidiEvent(false);
85
86
87
                if (! event) {
                    break;
                }
88
                packet = MIDIPacketListAdd(packet_list, sizeof(packet_buffer),
89
90
                                           packet,
                                           GetTimeStampFromFrames(event->time),
91
                                           event->size, event->buffer);
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
                if (! packet) {
                    break;
                }
            }
            SendPacketList(packet_list);
        } else {

            // We have a large system exclusive event.  We'll have to send it
            // out in multiple packets.
            size_t bytes_sent = 0;
            do {
                packet = MIDIPacketListInit(packet_list);
                assert(packet);
                size_t num_bytes = 0;
                for (; bytes_sent < size; bytes_sent += num_bytes) {
                    size_t num_bytes = size - bytes_sent;

                    // We use 256 because the MIDIPacket struct defines the
                    // size of the 'data' member to be 256 bytes.  I believe
                    // this prevents packets from being dynamically allocated
                    // by 'MIDIPacketListAdd', but I might be wrong.
                    if (num_bytes > 256) {
                        num_bytes = 256;
                    }
116
117
118
                    packet = MIDIPacketListAdd(packet_list,
                                               sizeof(packet_buffer), packet,
                                               timestamp, num_bytes,
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
                                               data + bytes_sent);
                    if (! packet) {
                        break;
                    }
                }
                if (! SendPacketList(packet_list)) {
                    // An error occurred.  The error message has already been
                    // output.  We lick our wounds and move along.
                    break;
                }
            } while (bytes_sent < size);
            event = 0;
        }
    }
    return false;
}

136
137
138
139
140
jack_midi_event_t *
JackCoreMidiOutputPort::GetCoreMidiEvent(bool block)
{
    if (! block) {
        if (sem_trywait(thread_queue_semaphore)) {
141
            if (errno != EAGAIN) {
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
                jack_error("JackCoreMidiOutputPort::Execute - sem_trywait: %s",
                           strerror(errno));
            }
            return 0;
        }
    } else {
        while (sem_wait(thread_queue_semaphore)) {
            if (errno != EINTR) {
                jack_error("JackCoreMidiOutputPort::Execute - sem_wait: %s",
                           strerror(errno));
                return 0;
            }
        }
    }
    return thread_queue->DequeueEvent();
}

159
MIDITimeStamp
160
JackCoreMidiOutputPort::GetTimeStampFromFrames(jack_nframes_t frames)
161
162
163
164
165
166
167
168
{
    return GetTimeFromFrames(frames) / time_ratio;
}

bool
JackCoreMidiOutputPort::Init()
{
    set_threaded_log_function();
169

Stephane Letz's avatar
Stephane Letz committed
170
    // OSX only, values read in RT CoreMidi thread
171
    UInt64 period = 0;
Stephane Letz's avatar
Stephane Letz committed
172
    UInt64 computation = 250 * 1000;
173
174
175
176
    UInt64 constraint = 500 * 1000;
    thread->SetParams(period, computation, constraint);

    if (thread->AcquireSelfRealTime()) {
177
178
179
180
181
182
183
184
        jack_error("JackCoreMidiOutputPort::Init - could not acquire realtime "
                   "scheduling.  Continuing anyway.");
    }
    return true;
}

void
JackCoreMidiOutputPort::Initialize(const char *alias_name,
185
186
187
188
                                   const char *client_name,
                                   const char *driver_name, int index,
                                   MIDIEndpointRef endpoint,
                                   SInt32 advance_schedule_time)
189
{
190
191
    JackCoreMidiPort::Initialize(alias_name, client_name, driver_name, index,
                                 endpoint, true);
192
193
    assert(advance_schedule_time >= 0);
    this->advance_schedule_time = advance_schedule_time;
194
195
196
197
198
199
200
201
}

void
JackCoreMidiOutputPort::ProcessJack(JackMidiBuffer *port_buffer,
                                   jack_nframes_t frames)
{
    read_queue->ResetMidiBuffer(port_buffer);
    for (jack_midi_event_t *event = read_queue->DequeueEvent(); event;
202
        event = read_queue->DequeueEvent()) {
203
204
205
206
        switch (thread_queue->EnqueueEvent(event, frames)) {
        case JackMidiWriteQueue::BUFFER_FULL:
            jack_error("JackCoreMidiOutputPort::ProcessJack - The thread "
                       "queue buffer is full.  Dropping event.");
207
            break;
208
209
210
211
        case JackMidiWriteQueue::BUFFER_TOO_SMALL:
            jack_error("JackCoreMidiOutputPort::ProcessJack - The thread "
                       "queue couldn't enqueue a %d-byte event.  Dropping "
                       "event.", event->size);
212
            break;
213
        default:
214
215
216
217
218
            if (sem_post(thread_queue_semaphore)) {
                jack_error("JackCoreMidiOutputPort::ProcessJack - unexpected "
                           "error while posting to thread queue semaphore: %s",
                           strerror(errno));
            }
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
        }
    }
}

bool
JackCoreMidiOutputPort::Start()
{
    bool result = thread->GetStatus() != JackThread::kIdle;
    if (! result) {
        result = ! thread->StartSync();
        if (! result) {
            jack_error("JackCoreMidiOutputPort::Start - failed to start MIDI "
                       "processing thread.");
        }
    }
    return result;
}

bool
JackCoreMidiOutputPort::Stop()
{
    bool result = thread->GetStatus() == JackThread::kIdle;
    if (! result) {
        result = ! thread->Kill();
        if (! result) {
            jack_error("JackCoreMidiOutputPort::Stop - failed to stop MIDI "
                       "processing thread.");
        }
    }
    return result;
}