Recent

Author Topic: [Solved] How to write an AVAudioEngine TapOnBus callback?  (Read 1618 times)

freq

  • New Member
  • *
  • Posts: 22
[Solved] How to write an AVAudioEngine TapOnBus callback?
« on: July 12, 2023, 01:26:23 pm »
Hi,

have an issue with a simple task to attach a tapBlock with the following function from an audio node (comment out the call and below sample code works fine)

inputNode.installTapOnBus_bufferSize_format_block(0,1024,nil,@tapBlock)

I don't know how to write a propper tapBlock callback function so that the following audio engine sequence doesn't crashes (I assume the tapBlock callback function is wrong defined or needs a Objective-C wrapper? Can't find any sample codes even for other OBjective-C callback cases). Any hint very welcome!

Thanks in advance!

Code: Pascal  [Select][+][-]
  1. unit Unit1;
  2.  
  3. {$mode delphi}
  4. {$modeswitch objectivec1}
  5. {$modeswitch cvar}
  6. {$packrecords c}
  7.  
  8.  
  9. {$linkframework AVFoundation}
  10.  
  11. interface
  12.  
  13. uses
  14.   Classes, SysUtils, FileUtil, Forms, Controls, Graphics, Dialogs,
  15.   StdCtrls,
  16.  
  17.   AVFoundation,
  18.   CoreAudio, MacTypes, SecBase,DefinedClassesFoundation;
  19.  
  20. type
  21.  
  22.   { TForm1 }
  23.  
  24.   TForm1 = class(TForm)
  25.     Button1: TButton;
  26.     Label1: TLabel;
  27.     procedure Button1Click(Sender: TObject);
  28.   private
  29.  
  30.     audioEngine: AVAudioEngine;
  31.     unitReverb:AVAudioUnitReverb;
  32.     inputNode:AVAudioInputNode;
  33.  
  34.     procedure SetupAudioEngine;
  35.  
  36.   public
  37.  
  38.   end;
  39.  
  40. var
  41.   Form1: TForm1;
  42.  
  43. implementation
  44.  
  45. {$R *.lfm}
  46.  
  47.  
  48. // issue probably in that definition, it is even never called
  49.  
  50. procedure tapBlock(buffer: AVAudioPCMBufferPtr; when:AVAudioTimePtr);
  51. var i:integer;
  52. begin
  53. i:=0;
  54. end;
  55.  
  56. { TForm1 }
  57.  
  58. procedure TForm1.Button1Click(Sender: TObject);
  59. begin
  60.       SetupAudioEngine;
  61. end;
  62.  
  63. procedure TForm1.SetupAudioEngine;
  64. var inputFormat: AVAudioFormat;
  65.     outError:NSError;
  66.  
  67. begin
  68.  
  69.  audioEngine:=  AVAudioEngine.alloc.init;
  70.  unitReverb:= AVAudioUnitReverb.alloc.init;
  71.  
  72.  audioEngine.attachNode(unitReverb);
  73.  
  74.  inputNode:= audioEngine.inputNode;
  75.  inputFormat:=  unitReverb.inputFormatForBus(0);
  76.  
  77.  inputNode.installTapOnBus_bufferSize_format_block(0,1024,nil,@tapBlock);       // this creates the crash!
  78.  
  79.  audioEngine.connect_to_format(inputNode,unitReverb, inputFormat);
  80.  audioEngine.connect_to_format(unitReverb,audioEngine.outputNode, inputFormat);
  81.  
  82. if audioEngine.startAndReturnError(@outError) then
  83.    begin
  84.    Label1.Caption:='ok';
  85.    end;
  86. end;
  87.  
  88. end.
  89.  

« Last Edit: July 15, 2023, 08:49:53 pm by freq »

Zvoni

  • Hero Member
  • *****
  • Posts: 2795
Re: How to write an AVAudioEngine TapOnBus callback?
« Reply #1 on: July 12, 2023, 01:45:02 pm »
Are you sure it's the Callback?
I'd rather look at the 3rd Parameter "Format" --> You pass nil

https://developer.apple.com/documentation/avfaudio/avaudionode/1387122-installtaponbus?language=objc
One System to rule them all, One Code to find them,
One IDE to bring them all, and to the Framework bind them,
in the Land of Redmond, where the Windows lie
---------------------------------------------------------------------
Code is like a joke: If you have to explain it, it's bad

freq

  • New Member
  • *
  • Posts: 22
Re: How to write an AVAudioEngine TapOnBus callback?
« Reply #2 on: July 12, 2023, 01:50:06 pm »
Are you sure it's the Callback?
I'd rather look at the 3rd Parameter "Format" --> You pass nil

https://developer.apple.com/documentation/avfaudio/avaudionode/1387122-installtaponbus?language=objc

Yes, checked, same crash with:

Code: Pascal  [Select][+][-]
  1. inputFormat:=  unitReverb.inputFormatForBus(0);
  2.  
  3.  tab:= @tapBlock;
  4.  unitReverb.installTapOnBus_bufferSize_format_block(0,1024,inputFormat,tab);       // this creates the crash!  

Assume that there must be something special in the way Objective-C handles a callback, is that "block" really a callback function or more an NSOBject something? 

Zvoni

  • Hero Member
  • *****
  • Posts: 2795
Re: How to write an AVAudioEngine TapOnBus callback?
« Reply #3 on: July 12, 2023, 02:06:42 pm »
No idea, since it's not my corner of expertise.

But your Procedure declaration looks good, IMO. Both arguments appear to be Pointers, and it expects a procedure (not a function)

The only thing i'm not sure about is "visibility" of your callback-procedure and/or calling-convention

@visibility: You have no "Forward"-Declaration under "Interface" --> But not sure about that.
@Calling-convention --> What's the standard calling-convention in Apple's objective C? cdecl?
One System to rule them all, One Code to find them,
One IDE to bring them all, and to the Framework bind them,
in the Land of Redmond, where the Windows lie
---------------------------------------------------------------------
Code is like a joke: If you have to explain it, it's bad

freq

  • New Member
  • *
  • Posts: 22
Re: How to write an AVAudioEngine TapOnBus callback?
« Reply #4 on: July 12, 2023, 02:20:11 pm »
@visibility: You have no "Forward"-Declaration under "Interface" --> But not sure about that.
@Calling-convention --> What's the standard calling-convention in Apple's objective C? cdecl?

Intresting ideas! Thanks, but unfortunately no success with both, I tried to add callback to interface and also tried with removed cdecl (though cdecl is the standard for objective C afaik). I'm still pretty sure that its a callback definition issue as it only crashes when activating the audio engine and when executed the installTapOnBus_bufferSize_format_block before.

Interesting that the block definition contains a ^ operator ... how to translate that?

typedef void (^AVAudioNodeTapBlock)(AVAudioPCMBuffer *buffer, AVAudioTime *when);
« Last Edit: July 12, 2023, 02:28:11 pm by freq »

Zvoni

  • Hero Member
  • *****
  • Posts: 2795
Re: How to write an AVAudioEngine TapOnBus callback?
« Reply #5 on: July 12, 2023, 02:34:31 pm »
Intresting ideas! Thanks, but unfortunately no success with both, I tried to add callback to interface and also tried with removed cdecl (though cdecl is the standard for objective C afaik). I'm still pretty sure that its a callback definition issue as it only crashes when activating the audio engine and when executed the installTapOnBus_bufferSize_format_block before.
You might be onto something here, because the standard calling convention in FPC is "Register", not cdecl.
I'd really research this.


Quote
Interesting that the block definition contains a ^ operator ... how to translate that?

typedef void (^AVAudioNodeTapBlock)(AVAudioPCMBuffer *buffer, AVAudioTime *when);
Usually this indicates a Function-Pointer, and you provide (correctly!) the "@"-Symbol in your call to it, but you're in mode Delphi!
IIRC, in mode delphi you shouldn't provide/need the "@"-Symbol, but i'm not sure about it, since i've never used mode delphi
try
Code: Pascal  [Select][+][-]
  1. unitReverb.installTapOnBus_bufferSize_format_block(0,1024,inputFormat,tapBlock); //No @-Symbol!

EDIT: Maybe a BINGO?
https://www.freepascal.org/docs-html/prog/progse74.html
Quote
D.3 Delphi mode

This mode is selected by the $MODE DELPHI switch. It tries to emulate, as closely as possible, the behavior of Delphi 4 till recent versions of Delphi . On the command line, this mode is selected by the -Mdelphi switch.

1. You cannot use the address operator to assign procedural variables.
"@"-Symbol is the Address-Operator
« Last Edit: July 12, 2023, 03:25:53 pm by Zvoni »
One System to rule them all, One Code to find them,
One IDE to bring them all, and to the Framework bind them,
in the Land of Redmond, where the Windows lie
---------------------------------------------------------------------
Code is like a joke: If you have to explain it, it's bad

freq

  • New Member
  • *
  • Posts: 22
Re: How to write an AVAudioEngine TapOnBus callback?
« Reply #6 on: July 12, 2023, 04:12:47 pm »

You might be onto something here, because the standard calling convention in FPC is "Register", not cdecl.
I'd really research this.

No, unfortunately no success with "Register"

Quote
Usually this indicates a Function-Pointer, and you provide (correctly!) the "@"-Symbol in your call to it, but you're in mode Delphi!

No, unfortunately also no luck to switch to {$mode objfpc}{$H+}  mode

 :(

Zvoni

  • Hero Member
  • *****
  • Posts: 2795
Re: How to write an AVAudioEngine TapOnBus callback?
« Reply #7 on: July 12, 2023, 05:02:07 pm »

No, unfortunately no success with "Register"

I think you misunderstood: if you don’t declare the calling convention, FreePascal uses „register“ by default.
Research if calling convention in Objective C is cdecl or something else
One System to rule them all, One Code to find them,
One IDE to bring them all, and to the Framework bind them,
in the Land of Redmond, where the Windows lie
---------------------------------------------------------------------
Code is like a joke: If you have to explain it, it's bad

freq

  • New Member
  • *
  • Posts: 22
Re: How to write an AVAudioEngine TapOnBus callback?
« Reply #8 on: July 13, 2023, 11:40:24 am »
I think you misunderstood: if you don’t declare the calling convention, FreePascal uses „register“ by default.
Research if calling convention in Objective C is cdecl or something else

Thanks for the clarification, but unfortunately with our without cdecl it doesn't work. 

freq

  • New Member
  • *
  • Posts: 22
Re: How to write an AVAudioEngine TapOnBus callback?
« Reply #9 on: July 15, 2023, 08:48:13 pm »
Thanks for all but I found the solution with the input from here that deals with a similar callback topic:
https://forum.lazarus.freepascal.org/index.php?topic=42001.45

As suspected it was a declaration topic and we have to use cblock:

This code works now without crashing  :):

Code: Pascal  [Select][+][-]
  1. unit Unit1;
  2.  
  3. {$mode objfpc}{$H+}
  4. {$modeswitch objectivec1}
  5. {$packrecords c}
  6. {$modeswitch cblocks}
  7.  
  8. {$linkframework AVFoundation}
  9.  
  10. interface
  11.  
  12. uses
  13.   Classes, SysUtils, FileUtil, Forms, Controls, Graphics, Dialogs,
  14.   StdCtrls,
  15.  
  16.   AVFoundation,
  17.   CoreAudio, MacTypes, SecBase,DefinedClassesFoundation;
  18.  
  19. type
  20.  
  21.   { TForm1 }
  22.  
  23.   TForm1 = class(TForm)
  24.     Button1: TButton;
  25.     Label1: TLabel;
  26.     procedure Button1Click(Sender: TObject);
  27.   private
  28.  
  29.     inputNode: AVAudioInputNode;
  30.     audioEngine: AVAudioEngine;
  31.     format: AVAudioFormat;
  32.  
  33.     mixerNode:AVAudioMixerNode;
  34.  
  35.     procedure SetupAudioEngine;
  36.  
  37.   public
  38.  
  39.     procedure tapBlock(buffer: AVAudioPCMBufferPtr; when:AVAudioTimePtr);
  40.  
  41.   end;
  42.  
  43.  
  44. var
  45.   Form1: TForm1;
  46.  
  47. implementation
  48.  
  49. {$R *.lfm}
  50.  
  51.  
  52. type
  53.   tblock = reference to procedure(buffer: AVAudioPCMBufferPtr; when:AVAudioTimePtr); cdecl; cblock;
  54.  
  55.  
  56. procedure TForm1.tapBlock(buffer: AVAudioPCMBufferPtr; when:AVAudioTimePtr);
  57. var i:integer;
  58. begin
  59. i:=0;
  60. end;
  61.  
  62. procedure TForm1.Button1Click(Sender: TObject);
  63. begin
  64.       SetupAudioEngine;
  65. end;
  66.  
  67. procedure TForm1.SetupAudioEngine;
  68. var
  69.     outError:NSError;
  70.     s:double;
  71.     tab: tblock;
  72.  
  73. begin
  74.  
  75.  tab:= @tapBlock;
  76.  
  77.  audioEngine:=  AVAudioEngine.alloc.init;
  78.  
  79.  inputNode:=audioEngine.inputNode;
  80.  
  81.  mixerNode:= AVAudioMixerNode.alloc.init;
  82.  audioEngine.attachNode(mixerNode);
  83.  format := inputNode.inputFormatForBus(0);
  84.  
  85.   mixerNode.installTapOnBus_bufferSize_format_block(0,2048,mixerNode.outputFormatForBus(0), OpaqueCBlock(tab)); // add tap
  86.  
  87.  audioEngine.connect_to_format(inputNode,mixerNode,format);
  88.  audioEngine.connect_to_format(mixerNode, audioEngine.outputNode, format);
  89.  
  90.  audioEngine.prepare;
  91.  
  92. if audioEngine.startAndReturnError(@outError) then
  93.    begin
  94.  
  95.    Label1.Caption:='ok';
  96.    end;
  97. end;
  98.  
  99. end.
  100.  
  101.  

 

TinyPortal © 2005-2018