Comments (7)
This does not appear to be an issue with csound-api. If I run this C program (which, of course, doesn’t depend on csound-api)—
#include <csound/csound.h>
#include <assert.h>
uintptr_t perform(void *data)
{
return csoundPerform((CSOUND *)data);
}
int main(int argc, char *argv[])
{
CSOUND *Csound = csoundCreate(NULL);
assert(csoundSetOption(Csound, "--output=dac") == CSOUND_SUCCESS);
char *orchestra =
"0dbfs = 1\n"
"instr 1\n"
" kenv line 0, p3, 1\n"
" asig oscil kenv, 220\n"
" ktrig metro 20\n"
" kmeter max_k asig, ktrig, 1\n"
" printk2 kmeter\n"
" chnset kmeter, \"meter\"\n"
"endin\n";
assert(csoundCompileOrc(Csound, orchestra) == CSOUND_SUCCESS);
char *score =
"i 1 0 1\n"
"i 1 1 1\n"
"i 1 2 1\n"
"e\n";
assert(csoundReadScore(Csound, score) == CSOUND_SUCCESS);
assert(csoundStart(Csound) == CSOUND_SUCCESS);
void *threadID = csoundCreateThread(perform, (void *)Csound);
size_t timeInterval = 50;
size_t maxTime = 3000;
for (unsigned int i = 0; i < maxTime / timeInterval; i++) {
MYFLT meterValue = csoundGetControlChannel(Csound, "meter", NULL);
printf("%.17f\n", meterValue);
csoundSleep(timeInterval);
}
csoundStop(Csound);
csoundJoinThread(threadID);
csoundDestroy(Csound);
return 0;
}
—I get, for all practical purposes, the same output that you get when running csound-api. From printk2
:
i1 0.00000
i1 0.04875
i1 0.09887
i1 0.14875
i1 0.19887
i1 0.24875
i1 0.29886
i1 0.34875
i1 0.39886
i1 0.44875
i1 0.49886
i1 0.54875
i1 0.59886
i1 0.64875
i1 0.69886
i1 0.74875
i1 0.79886
i1 0.84875
i1 0.89886
i1 0.94875
From logging the result of chnset
:
0.00000000000000000
0.00000000000000000
0.09886562427796818
0.09886562427796818
0.19886502865086275
0.19886502865086275
0.29886443302376170
0.29886443302376170
0.34875075720072124
0.39886383739666692
0.44875016157362646
0.54874956594653168
0.54874956594653168
0.64874897031943690
0.64874897031943690
0.74874837469234212
0.84874777906524734
0.84874777906524734
0.94874718343815256
0.94874718343815256
In addition, you can see how csound-api calls the underlying csoundGetControlChannel
function:
Lines 1151 to 1157 in 1d7e67e
There is no throttling of the output of csoundGetControlChannel
. The complexity of this function is from setting a JavaScript return value and an input object property.
My guess at the underlying issue is that csoundGetControlChannel
is (as the name suggests) for getting values of control signals, not volume metering, but as this issue appears to be independent of csound-api, there isn’t much that can be done about this in Node.js.
from csound-api.
Thank you SO MUCH, Nate! I really appreciate you taking the time to walk me through this even though it was totally independent of csound-api
. This is fascinating that this is the actual behavior of the Csound API. It still strikes me as surprising that outbound control signals can't be sent faster than 10 times per second. I'll take this issue to the Csound community for their comments.
Thanks again!
Jason
from csound-api.
I'll take this issue to the Csound community for their comments.
Good luck!
One more thing; your code example contains:
if (csound.Start(Csound) === csound.SUCCESS) {
csound.PerformAsync(Csound, () => csound.Destroy(Csound))
}
function readCsoundValues(){
let amplitude = csound.GetControlChannel(Csound, "meter")
console.log(amplitude)
setTimeout(readCsoundValues, 50)
}
readCsoundValues()
Doing this is risky. After the performance ends and csound.Destroy(Csound)
is called, you’ll be repeatedly passing a destroyed Csound
object to csound.GetControlChannel
, which will eventually lead to a segfault. You should probably do something like this (not tested):
if (csound.Start(Csound) === csound.SUCCESS) {
const intervalObj = setInterval(() => {
const amplitude = csound.GetControlChannel(Csound, 'meter')
console.log(amplitude)
}, 50)
csound.PerformAsync(Csound, () => {
clearInterval(intervalObj)
csound.Destroy(Csound)
})
}
from csound-api.
Hi Nate,
I posted about this on the Csound Forum, and Rory suggested this could be addressed with performKsmps
. Since I needed this to be non-blocking in Node.js I decided to try .PerformKsmpsAsync()
from csound-api
. Here's how I rewrote the program:
const csound = require('csound-api')
const Csound = csound.Create()
csound.SetOption(Csound, '--output=dac')
csound.CompileOrc(Csound, `
0dbfs = 1
ksmps = 50
instr 1
kenv line 0, p3, 1
asig oscil kenv, 220
ktrig metro 20
kmeter max_k asig, ktrig, 1
printk2 kmeter
chnset kmeter, "meter"
endin
`)
csound.ReadScore(Csound, `
i 1 0 1
i 1 1 1
i 1 2 1
e
`)
if (csound.Start(Csound) === csound.SUCCESS) {
csound.PerformKsmpsAsync(Csound, () => {
testKsmpsAsync()
}, () => {csound.Destroy(Csound)})
}
function testKsmpsAsync() {
let amplitude = csound.GetControlChannel(Csound, "meter")
console.log(amplitude)
}
With the metro
set to 20 times per second I got these results when calling .GetControlChannel()
and printing console.log(amplitude)
. There are still repeated values but the resolution is better.
0.051020104271887
0.051020104271887
0.15079275262579955
0.15079275262579955
0.2006790768027554
0.2006790768027554
0.25056571420852136
0.25056571420852136
0.30045210074777545
0.35033848728702954
0.4002253152814872
0.45011175684631904
0.5011319811739879
0.5510184227388174
0.5510184227388174
0.6009054387375272
0.6009054387375272
0.6507919279912071
0.7006784172448869
0.7006784172448869
0.8004520432202912
0.8004520432202912
0.9002256982093464
0.9002256982093464
0.9501122608305181
0.9501122608305181
Then when I set metro
to 100 I get these results which have the best resolution yet.
0.04027285494939373
0.0600906852729396
0.08049865311389513
0.15079275262579955
0.1802717774391319
0.25056571420852136
0.27097218735666745
0.3390020432191236
0.37074500053666504
0.4297047098505638
0.47051735542115114
0.5306118937342816
0.5510184227388174
0.6190439318322294
0.6394546067652224
0.6893411363710183
0.7097503581294351
0.7301585153919423
0.8004520432202912
0.850338572826087
0.9002256982093464
0.9297051470542858
0.9909208882845262
0.9909208882845262
Maybe you can help me understand one thing though. My understanding of .PerformKsmpsAsync()
is that every control cycle it yields control to the controlPeriodFunction
, in this case testKsmpsAsync()
. With sr = 41000
and ksmps = 50
that would mean there are 820 control cycles per second. However, console.log(amplitude)
only logs about 26 values per second on average.
Can you help me understand why the number of console logs is so much smaller than the number of control cycles? Is this just the inherent latency of the Node.js event loop? Or perhaps this is another case where this is actually the behavior of the Csound API and nothing to do with Node.js or csound-api
.
For the purposes of animating a volume meter 26 values per second is probably fine, but clearly I don't have a good mental model of how this is all supposed to work. I can also bring these questions to the Csound Forum going forward because I don't want to take too much of your time.
Thanks,
Jason
from csound-api.
Can you help me understand why the number of console logs is so much smaller than the number of control cycles?
I’ll give it my best shot!
This does have something to do with Node.js, but it seems to be a general limitation, not something specific to csound-api.
First, while your math is correct, Csound’s default sample rate is 44,100 Hz, not 41,000 Hz. This means there are in fact 882 control cycles per second (that is, kr
is 882) when ksmps
is 50. Thus, it seems reasonable to expect the progress function passed as the second argument to csound.PerformKsmpsAsync
to be called 882 times per second, after every 50 samples.
If you change your score to—
i 1 0 1
e
—so that Csound performs for 1 second, and the Csound performance section of your code to—
if (csound.Start(Csound) === csound.SUCCESS) {
let count = 0;
while (!csound.PerformKsmps(Csound)) {
count++;
}
csound.Destroy(Csound);
console.log(count);
}
—882 is logged, as expected. This at least tells us that csound-api is doing what it’s supposed to when csound.PerformKsmps
is used, but that function blocks the main thread.
If you change the Csound performance section to—
if (csound.Start(Csound) === csound.SUCCESS) {
let count = 0;
csound.PerformKsmpsAsync(
Csound,
() => { count++; },
() => {
csound.Destroy(Csound);
console.log(count);
}
);
}
—you should see that not only is a number (much) less than 882 logged, the number isn’t the same each time you run that code.
Here is where csound.PerformKsmpsAsync
runs csoundPerformKsmps
(the C function) in a loop:
Lines 678 to 686 in 1d7e67e
We can modify this code to log a count of control cycles without too much difficulty:
void Execute(const Nan::AsyncProgressWorker::ExecutionProgress& executionProgress) {
unsigned int count = 0;
while (!csoundPerformKsmps(wrapper->Csound)) {
count++;
executionProgress.Signal();
if (wrapper->eventHandler->CsoundDidPerformKsmps(wrapper->Csound))
break;
if (raisedSignal)
break;
}
printf("\n==> %u\n\n", count);
}
If you then rebuild csound-api (node-gyp rebuild
) and re-run the JavaScript code in Node.js, you should see towards the end of the output:
==> 882
This means that 882 control cycles really are being processed by csound-api. So what’s happening?
The “gotcha” seems to be this line:
Line 680 in 1d7e67e
This effectively tells Node.js to run the progress function passed as the second argument to csound.PerformKsmpsAsync
at some point; NAN’s documentation calls this “best-effort delivery”. So, if executionProgress.Signal()
runs after every 50 samples, that does not mean the progress function will actually run that often. It just means that Node.js has been told to run the progress function that often. How often the progress function actually runs is up to Node.js’ internals (specifically the libuv threading library, if memory serves).
from csound-api.
Hi Nate,
My apologies for my delayed response. Thank you again for taking the time to carefully explain all of this to me. I've learned a lot from you.
As an experiment, I used the OSCsend
opcode in my Csound instrument to send out amplitude values every k-cycle. I then ran an OSC server in the main process in Electron. I got beautiful high resolution values from that, easily 100+ values per second though I didn't test out the ceiling on that. Awesome!
All I had to do was pass the OSC values from the main process in Electron to the renderer process via inter-process communication. And there I got stuck with the same old bottleneck of 10 values/second. [face-palm]
So the quest continues, but this is clearly in the realm of Electron and nothing to do with csound-api
. I appreciate all your help and all the work you've put into the API! Thanks very much,
Jason
from csound-api.
No worries!
I appreciate all your help and all the work you've put into the API!
Thank you!
from csound-api.
Related Issues (17)
- Broken in Node 4.x HOT 7
- Allow working from global install HOT 3
- undefined symbol: __atomic_load_16** HOT 12
- undefined symbol on node 10.7 HOT 2
- Possibility of supressing stdout/stderr writes? HOT 2
- Problems installing HOT 5
- npm install csound-api not working HOT 17
- csdebug.h missing when installing on Debian Linux Jessie HOT 2
- Error with `npm install csound-api` HOT 4
- Errors running csound-api and Electron HOT 3
- error with boost when installing csound-api HOT 4
- Stopping/Resetting an instance of PerformAsync causes crash HOT 3
- Feature request and version error HOT 7
- Async .InputMessage? HOT 21
- Add CompileOrc and EvalCode to CsoundEventType HOT 3
- CSound subprocess stopped with SIGINT does not terminate properly HOT 10
Recommend Projects
-
React
A declarative, efficient, and flexible JavaScript library for building user interfaces.
-
Vue.js
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
-
Typescript
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
-
TensorFlow
An Open Source Machine Learning Framework for Everyone
-
Django
The Web framework for perfectionists with deadlines.
-
Laravel
A PHP framework for web artisans
-
D3
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
-
Recommend Topics
-
javascript
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
-
web
Some thing interesting about web. New door for the world.
-
server
A server is a program made to process requests and deliver data to clients.
-
Machine learning
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
-
Visualization
Some thing interesting about visualization, use data art
-
Game
Some thing interesting about game, make everyone happy.
Recommend Org
-
Facebook
We are working to build community through open source technology. NB: members must have two-factor auth.
-
Microsoft
Open source projects and samples from Microsoft.
-
Google
Google ❤️ Open Source for everyone.
-
Alibaba
Alibaba Open Source for everyone
-
D3
Data-Driven Documents codes.
-
Tencent
China tencent open source team.
from csound-api.