More > JRiver Media Center 32 for Linux
Linux, Mac, Windows Plugin Feature Request (MCWS need not apply)
ths61:
Can MC define a set of states supported by MC (Play, Pause, Stop, Gain=value, Mute, etc.) and then pass the state change transition notifications through EXISTING VST3 INTERFACES such as the following which implements sample-accurate parameter changes ?
Note: If this is implemented through the Steinberg VST3 interface, it would support 3 Operating Systems with a single interface already present on all 3.
TIA
--- Code: --- Advanced VST 3 techniques
On this page:
Part 1: Sample-accurate parameter handling
Part 2: Adding 32 and 64 bit audio processing
Part 3: Thread safe state changes
--- End code ---
https://steinbergmedia.github.io/vst3_dev_portal/pages/Tutorials/Advanced+VST+3+techniques.html#part-3-thread-safe-state-changes
Awesome Donkey:
Hmmm, isn't this what MCWS is for?
https://wiki.jriver.com/index.php/DevZone
ths61:
--- Quote from: Awesome Donkey on February 06, 2024, 02:51:02 pm ---Hmmm, isn't this what MCWS is for?
https://wiki.jriver.com/index.php/DevZone
--- End quote ---
Nope.
Too much latency rendering MCWS useless for accurate DSP plugin development.
Not sample accurate.
It also requires constant polling, which is very wasteful of system resources versus individual state change notifications.
Polling maybe suitable for initial DSP startup initialization, but falls apart at runtime for DSP.
If one is interested in 3 parameters, it equates to polling 3 web calls for every frame @ 768kHz ( 3 x 768,000 poll calls per second when not necessary ).
Does that make sense versus making a single notification, ONLY when a specific parameter of registered interest changes ?
Which would have a better chance of running on a RPi ?
Might be fine for updating a skin or displaying a title and track every 5 seconds, but not for frame accurate DSP state transitions.
It brings back nightmares of watching DOS programmers trying to code multi-IO port comm code on a preemptive multi-threaded RTOS using polling and then wondering why their throughput is so low and client latencies are so high causing comm timeouts, magnified retry counts and further network bottlenecks.
All they had to do on the RTOS was to register an interrupt callback on each of the IO ports and then go to sleep with no polling overhead waiting for IO to arrive on the respective ports and be processed in proper FIFIO order.
They decided to stick with their DOS centric polling "solution" and their work product painfully reflected their inferior design choice to the detriment of all features that used their comm interface.
In short, polling(MCWS) is the wrong tool for realtime DSP.
ths61:
--- Quote from: Awesome Donkey on February 06, 2024, 02:51:02 pm ---Hmmm, isn't this what MCWS is for?
https://wiki.jriver.com/index.php/DevZone
--- End quote ---
Here are some relative benchmarks:
--- Code: ---
Found: Volume 0.290000
Assumed: Not Muted (29% )
Found: State 2, RUN
Found: SampleRate 352800
Found: Channels 2
Found: Bitdepth 24
Found: ZoneID 0
Found: DurationMS 43066
Found: Status (Playin)
response:
<?xml version="1.0" encoding="UTF-8" standalone="yes" ?>
<Response Status="OK">
<Item Name="ZoneID">0</Item>
<Item Name="State">2</Item>
<Item Name="FileKey">22</Item>
<Item Name="NextFileKey">23</Item>
<Item Name="PositionMS">30503</Item>
<Item Name="DurationMS">43066</Item>
<Item Name="ElapsedTimeDisplay">0:30</Item>
<Item Name="RemainingTimeDisplay">-0:13</Item>
<Item Name="TotalTimeDisplay">0:43</Item>
<Item Name="PositionDisplay">0:30 / 0:43</Item>
<Item Name="PlayingNowPosition">0</Item>
<Item Name="PlayingNowTracks">4</Item>
<Item Name="PlayingNowPositionDisplay">1 of 4</Item>
<Item Name="PlayingNowChangeCounter">102</Item>
<Item Name="Bitrate">11163</Item>
<Item Name="Bitdepth">24</Item>
<Item Name="SampleRate">352800</Item>
<Item Name="Channels">2</Item>
<Item Name="Chapter">0</Item>
<Item Name="Volume">0.29</Item>
<Item Name="VolumeDisplay">29% (-35.5 dB)</Item>
<Item Name="ImageURL">MCWS/v1/File/GetImage?File=22</Item>
<Item Name="Artist">Tone Wik & Barokkanerne</Item>
<Item Name="Album">Bellezza Crudel - VIVALDI</Item>
<Item Name="Name">VIVALDI Cantata Rv 679: Che Giova Il Sospirar, Povero Core - Recitativo II</Item>
<Item Name="Status">Playing</Item>
</Response>
Average micros: 7552
--- End code ---
It takes @ 8ms to make a single MCWS poll request on a local machine, no remote calls.
Now for a wakeup call.
On the same machine, I can process 2 sub-frames of a thermal camera, scan and parse all of the thermal data (50,000 discrete temps), collate and print stats, plot 500 thermal points (TWICE), render Menus and Thermal Gradient OSD widgets, apply video filters, apply custom color map, scale with interpolation and render the composited video/graphics frame in the same or less time than it takes to make a single MCWS call to fetch @ a dozen parameter values.
1 frame @ 44.1kHz is 0.022ms, MCWS is @ 8ms.
Can you now see why MCWS is the wrong tool/api for realtime DSP ?
ths61:
For those on Linux that want to play with the latencies for yourselves, here is some quick sample code I pieced together.
It requires libcurl.
gcc -O3 -Wall -Wextra -o http_get http_get.c -lcurl
Usage: ./http_get http://localhost:52199/MCWS/v1/Playback/Info?Zone=0
UPDATE: Decoupled the print logs from the fetch and parse timings.
UPDATE: Decoupled curl init/cleanup from the fetch and parse timings.
--- Code: ---//
// https://girishjoshi.io/post/make-http-request-using-libcurl-in-c/
//
// gcc -ggdb3 -Wall -Wextra -o http_get http_get.c -lcurl
//
// ./http_get http://localhost:52199/MCWS/v1/Playback/Info?Zone=0
//
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h> // sleep() definition
#include <string.h>
#include <curl/curl.h>
#define DEFAULT_URL "http://localhost:52199/MCWS/v1/Playback/Info?Zone=0"
char *url = DEFAULT_URL;
// struct for holding http response.
typedef struct memory_struct {
char *buffer;
size_t size;
} MEMORY_STRUCT;
typedef struct {
double volume;
long state;
long sampleRate;
long channels;
long bitDepth;
long zoneID;
long durationMS;
long muted;
} JRMC_Data;
#define MILLIS_PER_SECOND 1000
#define MICROS_PER_SECOND (MILLIS_PER_SECOND * 1000)
#define NANOS_PER_SECOND (MICROS_PER_SECOND * 1000)
// timeval has Seconds and MICRO-seconds
// timespec has Seconds and NANO-seconds
void sleepMillis(long millis) {
struct timespec ts;
ts.tv_sec = (millis / MILLIS_PER_SECOND);
ts.tv_nsec = (millis % MILLIS_PER_SECOND) * 1000 * 1000;
nanosleep(&ts, NULL);
}
// Returns the current time in milliseconds.
int64_t currentTimeMillis() {
struct timeval currentTime;
gettimeofday(¤tTime, NULL);
int64_t s1 = (int64_t)(currentTime.tv_sec * 1000);
int64_t s2 = (int64_t)(currentTime.tv_usec / 1000);
return s1 + s2;
}
// Returns the current time in microseconds.
int64_t currentTimeMicros(){
#if 1 // More accurate (but slower) than clock_gettime(CLOCK_MONOTONIC_COARSE)
struct timeval currentTime;
gettimeofday(¤tTime, NULL);
return (currentTime.tv_sec * (int)1e6) + currentTime.tv_usec;
#else
// @ 4.5X faster that gettimeofday(), but may drift over time
struct timespec ts;
clock_gettime(CLOCK_MONOTONIC_COARSE, &ts);
return ( (ts.tv_sec * (int)1e6) +
(ts.tv_nsec / 1000) );
#endif
}
// write response data to the memory buffer.
static size_t
mem_write(void *contents, size_t size, size_t nmemb, void *userp){
// initialize MEMORY_STRUCT
size_t realsize = size * nmemb;
MEMORY_STRUCT *mem = (MEMORY_STRUCT *)userp;
char *ptr = realloc(mem->buffer, mem->size + realsize + 1);
if(ptr == NULL) {
/* out of memory! */
printf("not enough memory (realloc returned NULL)\n");
return 0;
}
// copy the response contents to memory_struct buffer.
mem->buffer = ptr;
memcpy(&(mem->buffer[mem->size]), contents, realsize);
mem->size += realsize;
mem->buffer[mem->size] = 0;
// return the size of content that is copied.
return realsize;
}
static CURL *curl_handle;
void http_get( const char *url, MEMORY_STRUCT *mem ){
mem->buffer = malloc(1);
mem->size = 0;
// specify url, callback function to receive response, buffer to hold
// response and lastly user agent for http request.
curl_easy_setopt(curl_handle, CURLOPT_URL, url);
curl_easy_setopt(curl_handle, CURLOPT_WRITEFUNCTION, mem_write);
curl_easy_setopt(curl_handle, CURLOPT_WRITEDATA, (void *)mem);
curl_easy_setopt(curl_handle, CURLOPT_USERAGENT, "custom-agent");
// make the http request.
CURLcode res = curl_easy_perform(curl_handle);
// check for errors.
if(res != CURLE_OK){
fprintf(stderr, "curl_easy_perform() failed: %s\n",
curl_easy_strerror(res));
}
}
#define STATE_STOP 0
#define STATE_PAUSE 1
#define STATE_PLAY 2
#define STATE_ABORT 3 // Status can be ABORTING and BUFFERING during STATE_ABORT
// NOTE: sizeof() used on these defintions, thus can NOT be const char *
const char VOLUME_STR[] = "<Item Name=\"Volume\">";
const char STATE_STR[] = "<Item Name=\"State\">";
const char SAMPLERATE_STR[] = "<Item Name=\"SampleRate\">"; // Can be -1
const char CHANNELS_STR[] = "<Item Name=\"Channels\">"; // Can be -1
const char BITDEPTH_STR[] = "<Item Name=\"Bitdepth\">"; // Can be -1
const char ZONEID_STR[] = "<Item Name=\"ZoneID\">";
const char DURATIONMS_STR[] = "<Item Name=\"DurationMS\">";
const char STATUS_STR[] = "<Item Name=\"Status\">"; // Playing, Stopping, Pause, Aborting, Buffering
const char VOLUMEDISPLAY_STR[] = "<Item Name=\"VolumeDisplay\">"; // To detect MUTE, look for Muted here !!!
void lameJRMCPollLoopApi( const char *url, MEMORY_STRUCT *m, JRMC_Data *jrmcData ) {
memset( m, 0, sizeof(*m) );
memset( jrmcData, 0, sizeof(*jrmcData) );
http_get( url, m );
// NOTE: Values can ( and will ) be -1
#if 0
Found: Volume 0.610000
Found: Muted (Not Muted)
Found: State 2, RUN
Found: SampleRate 192000
Found: Channels 2
Found: Bitdepth 24
Found: ZoneID 0
Found: DurationMS 202160
Found: Status (Playin)
#endif
char *ptr;
// MC doesn't provide proper, efficient callbacks, so ya works with whatcha gots ...
// Slow, Dirty, Error Prone, Shameless XML SCANNER/MINER in abscense of proper efficient callback API ...
if ( (ptr = strstr(m->buffer, VOLUME_STR)) ) {
jrmcData->volume = atof((ptr+sizeof(VOLUME_STR)-1));
}
if ( (ptr = strstr(m->buffer, VOLUMEDISPLAY_STR)) ) {
#define MUTED_STR "Muted"
#define MUTED_SIZE (sizeof(VOLUMEDISPLAY_STR) - 1)
jrmcData->muted = ( 0 == strncmp( (ptr+sizeof(VOLUMEDISPLAY_STR)-1), MUTED_STR, sizeof(MUTED_STR)-1) );
}
#define LONG_MAC(field, string) \
if ( (ptr = strstr(m->buffer, string)) ) { \
jrmcData->field = atol((ptr+sizeof(string)-1)); \
}
LONG_MAC( state, STATE_STR )
LONG_MAC( sampleRate, SAMPLERATE_STR )
LONG_MAC( channels, CHANNELS_STR )
LONG_MAC( bitDepth, BITDEPTH_STR )
LONG_MAC( zoneID, ZONEID_STR )
LONG_MAC( durationMS, DURATIONMS_STR )
}
void printJRMC_Data( const char *url, MEMORY_STRUCT *m, JRMC_Data *jrmcData ) {
char *ptr;
system("clear\n");
printf("\n%s\n\n", url);
printf("Found: Volume %f \n", jrmcData->volume );
printf("Found: Muted (%s)\n", (jrmcData->muted ? "Muted" : "Not Muted") );
printf("Found: State %ld, %s\n", jrmcData->state,
(STATE_STOP == jrmcData->state) ? "STOP" :
(STATE_PAUSE == jrmcData->state) ? "PAUSE" :
(STATE_PLAY == jrmcData->state) ? "RUN" :
(STATE_ABORT == jrmcData->state) ? "ABORT" :
"Unknown" );
printf("Found: SampleRate %ld \n", jrmcData->sampleRate );
printf("Found: Channels %ld \n", jrmcData->channels);
printf("Found: Bitdepth %ld \n", jrmcData->bitDepth );
printf("Found: ZoneID %ld \n", jrmcData->zoneID );
printf("Found: DurationMS %ld \n", jrmcData->durationMS );
if ( (ptr = strstr(m->buffer, STATUS_STR)) ) {
printf("Found: Status (%6.6s)\n", (ptr+sizeof(STATUS_STR)-1));
}
printf("\nresponse:\n%s\n", m->buffer);
}
int main( int argc, char** args ){
// check number of arguments.
if ( 2 == argc ) {
url = args[1];
}
printf("trying to make http request to: %s\n", url);
long counter = 0;
int64_t t1, total = 0;
JRMC_Data jrmcData;
MEMORY_STRUCT m;
// initialize curl
curl_global_init(CURL_GLOBAL_ALL);
curl_handle = curl_easy_init();
while ( 1 ) {
counter++;
t1 = currentTimeMicros();
lameJRMCPollLoopApi( url, &m, &jrmcData );
total += ( currentTimeMicros() - t1 );
printJRMC_Data( url, &m, &jrmcData );
if ( m.buffer ) {
free(m.buffer);
m.buffer = 0x00;
}
printf("\nAverage micros: %ld\n", total/counter );
sleep(1);
}
// cleanup curl
curl_easy_cleanup(curl_handle);
curl_global_cleanup();
return 0;
}
--- End code ---
Navigation
[0] Message Index
[#] Next page
Go to full version