0

My question is a little tricky, and I'm not exactly experienced (I might get some terms wrong), so here goes. I'm declaring an instance of an object called "Singer". The instance is called "singer1". "singer1" produces an audio signal. Now, the following is the code where the specifics of the audio signal are determined:

OSStatus playbackCallback(void *inRefCon,
                      AudioUnitRenderActionFlags *ioActionFlags,
                      const AudioTimeStamp *inTimeStamp,
                      UInt32 inBusNumber, 
                      UInt32 inNumberFrames,
                      AudioBufferList *ioData) {    

//Singer *me = (Singer *)inRefCon;

static int phase = 0;

for(UInt32 i = 0; i < ioData->mNumberBuffers; i++) {

    int samples = ioData->mBuffers[i].mDataByteSize / sizeof(SInt16);

    SInt16 values[samples];

    float waves;
    float volume=.5;

    for(int j = 0; j < samples; j++) {


        waves = 0;


        waves += sin(kWaveform * 600 * phase)*volume;
        waves += sin(kWaveform * 400 * phase)*volume;
        waves += sin(kWaveform * 200 * phase)*volume;
        waves += sin(kWaveform * 100 * phase)*volume;            

        waves *= 32500 / 4; // <--------- make sure to divide by how many waves you're stacking

        values[j] = (SInt16)waves;
        values[j] += values[j]<<16;

        phase++;

    }

    memcpy(ioData->mBuffers[i].mData, values, samples * sizeof(SInt16));

}

return noErr;

}

99% of this is borrowed code, so I only have a basic understanding of how it works (I don't know about the OSStatus class or method or whatever this is. However, you see those 4 lines with 600, 400, 200 and 100 in them? Those determine the frequency. Now, what I want to do (for now) is insert my own variable in there in place of a constant, which I can change on a whim. This variable is called "fr1". "fr1" is declared in the header file, but if I try to compile I get an error about "fr1" being undeclared. Currently, my technique to fix this is the following: right beneath where I #import stuff, I add the line

fr1=0.0;//any number will work properly

This sort of works, as the code will compile and singer1.fr1 will actually change values if I tell it to. The problems are now this:A)even though this compiles and the tone specified will play (0.0 is no tone), I get the warnings "Data definition has no type or storage class" and "Type defaults to 'int' in declaration of 'fr1'". I bet this is because for some reason it's not seeing my previous declaration in the header file (as a float). However, again, if I leave this line out the code won't compile because "fr1 is undeclared". B)Just because I change the value of fr1 doesn't mean that singer1 will update the value stored inside the "playbackcallback" variable or whatever is in charge of updating the output buffers. Perhaps this can be fixed by coding differently? C)even if this did work, there is still a noticeable "gap" when pausing/playing the audio, which I need to eliminate. This might mean a complete overhaul of the code so that I can "dynamically" insert new values without disrupting anything. However, the reason I'm going through all this effort to post is because this method does exactly what I want (I can compute a value mathematically and it goes straight to the DAC, which means I can use it in the future to make triangle, square, etc waves easily). I have uploaded Singer.h and .m to pastebin for your veiwing pleasure, perhaps they will help. Sorry, I can't post 2 HTML tags so here are the full links. (http://pastebin.com/ewhKW2Tk) (http://pastebin.com/CNAT4gFv)

So, TL;DR, all I really want to do is be able to define the current equation/value of the 4 waves and re-define them very often without a gap in the sound. Thanks. (And sorry if the post was confusing or got off track, which I'm pretty sure it did.)

wyager
  • 101
  • 8

1 Answers1

1

My understanding is that your callback function is called every time the buffer needs to be re-filled. So changing fr1..fr4 will alter the waveform, but only when the buffer updates. You shouldn't need to stop and re-start the sound to get a change, but you will notice an abrupt shift in the timbre if you change your fr values. In order to get a smooth transition in timbre, you'd have to implement something that smoothly changes the fr values over time. Tweaking the buffer size will give you some control over how responsive the sound is to your changing fr values.

Your issue with fr being undefined is due to your callback being a straight c function. Your fr variables are declared as objective-c instance variables as part of your Singer object. They are not accessible by default.

take a look at this project, and see how he implements access to his instance variables from within his callback. Basically he passes a reference to his instance to the callback function, and then accesses instance variables through that.

https://github.com/youpy/dowoscillator

notice:

Sinewave *sineObject = inRefCon;
float freq = sineObject.frequency * 2 * M_PI / samplingRate;

and:

AURenderCallbackStruct input;
input.inputProc = RenderCallback;
input.inputProcRefCon = self;

Also, you'll want to move your callback function outside of your @implementation block, because it's not actually part of your Singer object.

You can see this all in action here: https://github.com/coryalder/SineWaver

Kenny Winker
  • 11,919
  • 7
  • 56
  • 78
  • Hey Kenny,Thanks for your answer. I think I understand what he's doing, is he turning the objective c variables into a pointer that the C code can access? The -> operator is like "." but for a pointer, right? Anyway, @defs() does not work anymore... Xcode says "@defs is not supported in new abi". What can I do instead? Also, how often does the buffer update? Because I haven't heard the sound change even though I've confirmed that the value of singer1.fr1 has changed (by turning it into a string and passing it to a UILabel). Also, where should I move the callback function to? Thank you so much! – wyager Nov 28 '10 at 20:04
  • I edited it to be more accurate... the whole @defs thing is making a c-struct out of an objective c object... not the best way to do it. If you have properties set up you can just use objective c dot notation to access the values. Anyway, to make sure I wasn't just blowing smoke, I set this up in a new project to get it all working. Check it out https://github.com/coryalder/SineWaver – Kenny Winker Nov 28 '10 at 23:47
  • I think the default buffer is 1024 bytes.. you can hear if you move the sliders fast in my SineWaver project the pops as the waveform changes abruptly each time the buffer re-fills. – Kenny Winker Nov 28 '10 at 23:48
  • Thank you so much! I should be able to work with this... and I'll try to implement a smoothing routine to get rid of the pops. Is there any way to decrease the buffer size though? I would think it would be easier... or would that mess it up? – wyager Nov 29 '10 at 00:00
  • You can change the buffer size, but the smaller the buffer the harder your app hits the cpu. Audio processing is already bad for battery life. I'm also not sure you'd be able to make it so small you never got pops. Worth experimenting with. Implementing a portamento effect in code is probably the best thing to do. – Kenny Winker Nov 29 '10 at 00:11
  • Ok, thanks. In five hours (since I posted) I've done a complete overhaul of the app (another 50 or 60k of code too), everything works great besides the "pop". I still have to figure out a way to actually smooth out the frequency... Maybe when I change the value of fr1 I can run a "for" loop with x number of frequency steps, and if I add a delay of some kind inside the "for" loop it will give the illusion of a "slow", smooth frequency change. But are you sure that the pop sound is caused by rapid frequency change and not just lag in the software? – wyager Nov 29 '10 at 05:55
  • Pretty sure. Think about it... the buffer fills with a number of frames of audio at one frequency. Next time the buffer fills, the frequency is dramatically different. The waves will not be in phase with where they were in the frame previous. The result will be a pop sound. Now that I think about it, a way to change the frequency without a pop or a portamento effect would be to keep the old fr value until the wave is crossing zero. And then force the new wave's phase to be at zero as well. Not sure exactly how to implement that, but it's a strategy. – Kenny Winker Nov 30 '10 at 01:46
  • Hmm... It appears the problem also exists when I implement non-sinusoidal wavetypes like square waves. – wyager Nov 30 '10 at 04:47
  • Oops, didn't realize enter would add my comment. For example, using the wave equation "waves *= round(sin(kWaveform * (me.fr4/100) * phase)+.5);" to get a half-square-wave results in a loud "pop" at every "sharp corner" in the wave. I'm going to go try phase matching for a few minutes then call it a day. – wyager Nov 30 '10 at 04:51
  • Dangit, again with the enter key. Ok, so I believe I got the phase matched. Inside the callback method, I put me.phase2=phase. (phase2 is an obj-c int) Then outside, in the slider loop, I put do{}while(phase2%44100!=1). While this only updated the audio or the UI once a second (I think on-phase), I still got that "pop". If I'm matching phases wrong, let me know... – wyager Nov 30 '10 at 05:13
  • I also tried adding and if(phase%44100=1){} statement in the callback assigning new values to the frequencies but this also just popped loudly every second. – wyager Nov 30 '10 at 05:15