summaryrefslogtreecommitdiffstatshomepage
path: root/3rdparty/portmidi/pm_test/multivirtual.c
blob: 059bc50e4f9db5b5a71f8cab7d77de7d575f7779 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
/* multivirtual.c -- test for creating two input and two output virtual ports */
/*
 * Roger B. Dannenberg
 * Oct 2021
 */
#include "portmidi.h"
#include "porttime.h"
#include "stdlib.h"
#include "stdio.h"
#include "string.h"
#include "assert.h"

#define OUTPUT_BUFFER_SIZE 0
#define DEVICE_INFO NULL
#define DRIVER_INFO NULL
#define TIME_PROC ((PmTimeProcPtr) Pt_Time)
#define TIME_INFO NULL
#define TIME_START Pt_Start(1, 0, 0) /* timer started w/millisecond accuracy */

int latency = 0;

static void prompt_and_exit(void)
{
    printf("type ENTER...");
    while (getchar() != '\n') ;
    /* this will clean up open ports: */
    exit(-1);
}


static PmError checkerror(PmError err)
{
    if (err == pmHostError) {
        /* it seems pointless to allocate memory and copy the string,
         * so I will do the work of Pm_GetHostErrorText directly
         */
        char errmsg[80];
        Pm_GetHostErrorText(errmsg, 80);
        printf("PortMidi found host error...\n  %s\n", errmsg);
        prompt_and_exit();
    } else if (err < 0) {
        printf("PortMidi call failed...\n  %s\n", Pm_GetErrorText(err));
        prompt_and_exit();
    }
    return err;
}

static int msg_count[2] = {0, 0};

void poll_input(PmStream *in, int which)
{
    PmEvent buffer[1];
    int pitch, expected, length;
    PmError status = Pm_Poll(in);
    if (status == TRUE) {
        length = Pm_Read(in, buffer, 1);
        if (length > 0) {
            printf("Got message %d from portmidi%d: "
                   "time %ld, %2x %2x %2x\n",
                   msg_count[which], which + 1, (long) buffer[0].timestamp,
                   (status = Pm_MessageStatus(buffer[0].message)),
                   (pitch = Pm_MessageData1(buffer[0].message)),
                   Pm_MessageData2(buffer[0].message));
            if (status == 0x90) {  /* 1 & 2 are on/off 60, 3 & 4 are 61, etc. */
                expected = (((msg_count[which] - 1) / 2) % 12) + 60 +
                           which * 12;
                if (pitch != expected) {
                    printf("WARNING: expected pitch %d, got pitch %d\n",
                           expected, pitch);
                }
            }
            msg_count[which]++;
        } else {
            assert(0);
        }
    }
}


void wait_until(PmTimestamp when, PmStream *in1, PmStream *in2)
{
    while (when > Pt_Time()) {
        poll_input(in1, 0);
        poll_input(in2, 1);
        Pt_Sleep(10);
    }
}


/* create one virtual output device and one input device */
void init(const char *name, PmStream **midi_out, PmStream **midi_in,
          int *id_out, int *id_in)
{
    PmEvent buffer[1];

    *id_out = checkerror(Pm_CreateVirtualOutput(name, NULL, DEVICE_INFO));
    checkerror(Pm_OpenOutput(midi_out, *id_out, DRIVER_INFO, OUTPUT_BUFFER_SIZE,
                             TIME_PROC, TIME_INFO, latency));
    printf("Virtual Output \"%s\" id %d created and opened.\n", name, *id_out);
    
    *id_in = checkerror(Pm_CreateVirtualInput(name, NULL, DRIVER_INFO));
    checkerror(Pm_OpenInput(midi_in, *id_in, NULL, 0, NULL, NULL));
    printf("Virtual Input \"%s\" id %d created and opened.\n", name, *id_in);
    Pm_SetFilter(*midi_in, PM_FILT_ACTIVE | PM_FILT_CLOCK | PM_FILT_SYSEX);
    /* empty the buffer after setting filter, just in case anything
       got through */
    while (Pm_Read(*midi_in, buffer, 1)) ;
}


void main_test(int num)
{
    PmStream *midi1_out;
    PmStream *midi2_out;
    PmStream *midi1_in;
    PmStream *midi2_in;
    int id1_out;
    int id2_out;
    int id1_in;
    int id2_in;
    int32_t next_time;
    PmEvent buffer[1];
    PmTimestamp timestamp;
    int pitch = 60;
    int expected_count = num + 1;  /* add 1 for MIDI Program message */

    /* It is recommended to start timer before Midi; otherwise, PortMidi may
       start the timer with its (default) parameters
     */
    TIME_START;

    init("portmidi1", &midi1_out, &midi1_in, &id1_out, &id1_in);
    init("portmidi2", &midi2_out, &midi2_in, &id2_out, &id2_in);
    
    printf("Type ENTER to send messages: ");
    while (getchar() != '\n') ;

    buffer[0].timestamp = Pt_Time();
#define PROGRAM 0
    buffer[0].message = Pm_Message(0xC0, PROGRAM, 0);
    Pm_Write(midi1_out, buffer, 1);
    Pm_Write(midi2_out, buffer, 1);
    next_time = Pt_Time() + 1000;  /* wait 1s */
    while (num > 0) {
        wait_until(next_time, midi1_in, midi2_in);
        Pm_WriteShort(midi1_out, next_time, Pm_Message(0x90, pitch, 100));
        Pm_WriteShort(midi2_out, next_time, Pm_Message(0x90, pitch + 12, 100));
        printf("Note On pitch %d\n", pitch);
        num--;
        next_time += 500;

        wait_until(next_time, midi1_in, midi2_in);
        Pm_WriteShort(midi1_out, next_time, Pm_Message(0x90, pitch, 0));
        Pm_WriteShort(midi2_out, next_time, Pm_Message(0x90, pitch + 12, 0));
        printf("Note Off pitch %d\n", pitch);
        num--;
        pitch = (pitch + 1) % 12 + 60;
        next_time += 500;
    }
    wait_until(next_time, midi1_in, midi2_in);  /* get final note-offs */

    printf("Got %d messages from portmidi1 and %d from portmidi2; "
           "expected %d.\n", msg_count[0], msg_count[1], expected_count);
    
    /* close devices (this not explicitly needed in most implementations) */
    printf("ready to close...");
    checkerror(Pm_Close(midi1_out));
    checkerror(Pm_Close(midi2_out));
    checkerror(Pm_Close(midi1_in));
    checkerror(Pm_Close(midi2_in));
    printf("done closing.\nNow delete the virtual devices...");
    checkerror(Pm_DeleteVirtualDevice(id1_out));
    checkerror(Pm_DeleteVirtualDevice(id1_in));
    checkerror(Pm_DeleteVirtualDevice(id2_out));
    checkerror(Pm_DeleteVirtualDevice(id2_in));
    printf("done deleting.\n");
}


void show_usage()
{
    printf("Usage: multivirtual [-h] [-l latency-in-ms] [n]\n"
           "    -h for this message,\n"
           "    -l ms designates latency for precise timing (default 0),\n"
           "    n is number of message to send each output, not counting\n"
           "        initial program change.\n"
           "sends change program to 1, then one note per second with 0.5s on,\n"
           "0.5s off, for n/2 seconds to both output ports portmidi1 and\n"
           "portmidi2. portmidi1 gets pitches from C4 (60). portmidi2 gets\n"
           "pitches an octave higher. Latency >0 uses the device driver for \n"
           "precise timing (see PortMidi documentation). Inputs print what\n"
           "they get and print WARNING if they get something unexpected.\n"
           "The expected test is use two instances of testio to loop\n"
           "portmidi1 back to portmidi1 and portmidi2 back to portmidi2.\n"); 
    exit(0);
}


int main(int argc, char *argv[])
{
    int num = 10;
    int i;
    for (i = 1; i < argc; i++) {
        if (strcmp(argv[i], "-h") == 0) {
            show_usage();
        } else if (strcmp(argv[i], "-l") == 0 && (i + 1 < argc)) {
            i = i + 1;
            latency = atoi(argv[i]);
            printf("Latency will be %d\n", latency);
        } else {
            num = atoi(argv[1]);
            if (num <= 0) {
                show_usage();
            }
            printf("Sending %d messages.\n", num);
        }
    }

    main_test(num);
    
    printf("finished sendvirtual test...type ENTER to quit...");
    while (getchar() != '\n') ;
    return 0;
}