0

I'm working on a project in which I'm additively re-synthesizing a pipe Organ in C. I took samples of the original, approximated them into lists of frequency and amplitude (the necessary variables to recreate any harmonic spectra)

I have a functioning model running in the command line, but the current Issue I'm running up against is that I am adding sine functions together in real time, creating a very computationally expensive synthesis engine (I have to run at 2048 sample buffer with only 4 voices to avoid timeout errors.

each stop variable looks like the one below, for 12 stops in total, coming out to somewhere just over 100 sine functions, all processing in real time.

clarFour = (0.5 *
                (sin(voice *  M_PI * 2.0f * 1.0f  * 4.0f ) * 0.0901f)+
                (sin(voice *  M_PI * 2.0f * 2.0f  * 4.0f ) * 0.108f)+
                (sin(voice *  M_PI * 2.0f * 3.0f  * 4.0f ) * 0.0609f)+
                (sin(voice *  M_PI * 2.0f * 4.0f  * 4.0f ) * 0.0319f)+
                (sin(voice *  M_PI * 2.0f * 5.0f  * 4.0f ) * 0.0184f)+
                (sin(voice *  M_PI * 2.0f * 6.0f  * 4.0f ) * 0.0393f)+
                (sin(voice *  M_PI * 2.0f * 7.0f  * 4.0f ) * 0.0350f)+
                (sin(voice *  M_PI * 2.0f * 8.0f  * 4.0f ) * 0.0555f)+
                (sin(voice *  M_PI * 2.0f * 9.0f  * 4.0f ) * 0.0331f)+
                (sin(voice *  M_PI * 2.0f * 10.0f * 4.0f ) * 0.0412f)+
                (sin(voice *  M_PI * 2.0f * 11.0f * 4.0f ) * 0.0263f)+
                (sin(voice *  M_PI * 2.0f * 12.0f * 4.0f ) * 0.0151f)+
                (sin(voice *  M_PI * 2.0f * 13.0f * 4.0f ) * 0.0123f)+
                (sin(voice *  M_PI * 2.0f * 15.0f * 4.0f ) * 0.00237f)+
                (sin(voice *  M_PI * 2.0f * 16.0f * 4.0f ) * 0.00139f));

and then adding each stop variable in a theta variable that in turn does the actual synthesis sample-by-sample.

theta =  bourEight +
             bourFour  +
             clarFour  +
             fifTwo    +
             melEight  +
             mixOne    +
             octFour   +
             prinEight +
             truEight  +
             truSixt   +
             tweTwo    +
             vioEight;

    sine = theta;

    for(int c = 0; c < kNumChannels; c++){
      buffer[n + c] += pow(sineWave->amplitude[q], 0.25) * sine;
    }

    sineWave->phase[q] += sineWave->frequency[q];

    if(sineWave->phase[q] >= 1.0f){
      sineWave->phase[q] -= 1.0f;
    }

is there a good way to package this up into something that doesn't eat CPU like cheerios? each stop is a set waveform anyways, so is there a better way computationally to approximate an arbitrary waveform without adding so many sine functions together?

ideally I'd like to do this without completely having to rebuild my synthesis engine, but I recognize that may be having my cake and eating it too.

the full project can be viewed at https://github.com/Natameme/NatalieHogue_EP_353_Classwork/blob/main/Final/Source/StMaximilliansOrganMK1.c

it only requires the external libraries for PortAudio and PortMidi to run

  • If there is a discrete number of values for `voice` you could use a precalcaluted 2D look-up table for each `sin(voice * M_PI * 2.0f * xxx * 4.0f )` – Weather Vane Dec 14 '21 at 18:41
  • Even if it is not discrete, you can perform simple interpolation – Eugene Sh. Dec 14 '21 at 19:22
  • Quite often, a variety of optimizations are possible. Unfortunately, you have shown little of the context. For starters, assuming `voice` is some variable that changes, rewrite `sin(voice * M_PI * 2.0f * 1.0f * 4.0f )` as `sin(M_PI * 2.0f * 1.0f * 4.0f * voice)`. That makes `M_PI * 2.0f * 1.0f * 4.0f` a compile-time constant the compiler can calculate once instead of each time it is used in the program. In contrast, `voice * M_PI * 2.0f * 3.0f * 4.0f` has to perform multiple multiplications, because the rounding errors may cause different results, and the compiler is not allowed to… – Eric Postpischil Dec 14 '21 at 19:42
  • … change the order unless a permissive floating-point arithmetic mode is turned on. – Eric Postpischil Dec 14 '21 at 19:42
  • 1
    Then add context to the question about what `voice` is and how it changes. Also explain what the relationships are between the various constants, such as `.0901f`, `0.108f`, and so on. There may be patterns in them that can be exploited. – Eric Postpischil Dec 14 '21 at 19:43
  • 1
    `sqrt(sqrt(sineWave->amplitude[q]))` might be faster than `pow(sineWave->amplitude[q], .25)`, but that requires checking. – Eric Postpischil Dec 14 '21 at 19:44
  • Looks like a task for fast fourier transform – tstanisl Dec 14 '21 at 20:04
  • 2
    @tstanisl: That occurred to me too, but OP has not shown enough information yet. Also, the Hakmem Minsky circle algorithm might be useful. – Eric Postpischil Dec 14 '21 at 20:18
  • Related: https://stackoverflow.com/questions/69729326/endless-sine-generation-in-c – Peter O. Dec 14 '21 at 20:31
  • TO CLARIFY, voice is a phase pointer that increments between -1.0 and 1.0 per cycle. – NatalieKrunch Dec 14 '21 at 21:09
  • MORE CLARITY: the per stop structure is sin(/*phase*/voice * /*tau*/(M_PI * 2.0f)* /*harmonic*/1.0f * /*octave*/ 4.0f ) * /*amplitude*/0.0901f)+ – NatalieKrunch Dec 14 '21 at 21:18
  • Every time I see `sin (M_PI * )` my first thought is "Does this platform offer a `sinpi()` function?" and if so, use that. Very likely faster and more accurate, but by all means benchmark and profile. What are the limits on the range of `voice`? Is `float` computation accurate enough for this use case? Likely to be noticeably faster then `double` computation. `M_PI` is usually a `double` literal constant, turning most of the computation into `double` computation. Make it "float clean". – njuffa Dec 16 '21 at 23:54

0 Answers0