Custom streams with FMOD

Windows specific forum
BackupUser
PureBasic Guru
PureBasic Guru
Posts: 16777133
Joined: Tue Apr 22, 2003 7:42 pm

Post by BackupUser »

Restored from previous forum. Originally posted by mindplay.

I've been trying to implement a custom stream in FMOD - that is, I wish to generate my own sound in real time, via a callback buffer, and have FMOD play it back over the soundcard.

For starters, I just want to see that it works, i.e. by filling the buffer with zeroes, but whenever I call FSOUND_Stream_Play, it just crashes.

I suspect I didn't implement the callback with the correct interface? ... I even tried making a callback that does nothing at all, not even zeroing the buffer, but it still crashes.

Any help would be greatly appreciated...

Code: Select all

; FMOD Constants

#FSOUND_16BITS.l = $00000010
#FSOUND_SIGNED.l = $00000100
#FSOUND_MONO.l = $00000020

#FSOUND_FREE.l = -1

; Buffer Structure

#BufferSize.l = 1000*2*2

Structure BufferType
  Sample.w[#BufferSize]
EndStructure

; Callback

Procedure.b MyStreamCallback(*Stream, *Buff.BufferType, Len.l, Param.l)
  For Index.l = 0 To #BufferSize-1
    *Buff\Sample[Index] = 0
  Next
  ProcedureResult = #true
EndProcedure

; Open a console, and play the stream until ENTER is pressed

OpenConsole()

PrintN("Initializing FMOD...")

FSOUND_Init(44100, 32, 0)

MyStream = FSOUND_Stream_Create(@MyStreamCallback, #BufferSize, #FSOUND_16BITS | #FSOUND_SIGNED | #FSOUND_MONO, 44100, 1)

FSOUND_Stream_Play(#FSOUND_FREE, MyStream)

Print("Press ENTER to quit")
Input()

FSOUND_Stream_Close(MyStream)

FSOUND_Close()

CloseConsole()

BackupUser
PureBasic Guru
PureBasic Guru
Posts: 16777133
Joined: Tue Apr 22, 2003 7:42 pm

Post by BackupUser »

Restored from previous forum. Originally posted by Danilo.

Use FMOD DLL with STDCALL callbacks: http://home.t-online.de/home/ExpressTrack/PureFMOD.zip

> For Index.l = 0 To #BufferSize-1
> *Buff\Sample[Index] = 0
> Next
> ProcedureResult = #true

Should this loop be 'For Index = 0 to Len - 1' ??

cya,
...Danilo
(registered PureBasic user)
BackupUser
PureBasic Guru
PureBasic Guru
Posts: 16777133
Joined: Tue Apr 22, 2003 7:42 pm

Post by BackupUser »

Restored from previous forum. Originally posted by J. Baker.

not sure if this helps but when i tried to run the code it said...
Line 31:FSOUND_Init(44100, 32, 0)is not a function, an array or a linked list
BackupUser
PureBasic Guru
PureBasic Guru
Posts: 16777133
Joined: Tue Apr 22, 2003 7:42 pm

Post by BackupUser »

Restored from previous forum. Originally posted by Danilo.

ummm... sorry.

>Structure BufferType
> Sample.w[#BufferSize]
>EndStructure

This Structure must be Sample.b for bytes!

cya,
...Danilo
(registered PureBasic user)
BackupUser
PureBasic Guru
PureBasic Guru
Posts: 16777133
Joined: Tue Apr 22, 2003 7:42 pm

Post by BackupUser »

Restored from previous forum. Originally posted by mindplay.

J.Baker: If you don't have FMOD installed, that's the error message you'd get, yes.

Danilo: bytes? but as you can see, when I create the stream, I specify #FSOUND_16BITS and #FSOUND_SIGNED - which should be suitable for the ".w" type in PB, which is a 16-bit signed integer type, or not?
BackupUser
PureBasic Guru
PureBasic Guru
Posts: 16777133
Joined: Tue Apr 22, 2003 7:42 pm

Post by BackupUser »

Restored from previous forum. Originally posted by mindplay.

I see what you meant by the buffersize problem ... should be fixed in the following code - that's not why it crashes though, it never even gets to that point in the code ... as explained, even if you comment out every line inside the callback procedure, it still crashes as soon as FSOUND_Stream_Play is called ... I added code now to check if FSOUND_Stream_Create returns a zero pointer, which would mean that stream creation failed - it does not, however.

You say to use STDCALL, but how can I specicy the calling convention for my callback procedure? ... the FMOD.DLL and the PB library I'm using is the one I donwloaded from the link you provided ... does it work on your machine???

Code: Select all

; FMOD Constants

#FSOUND_16BITS.l = $00000010
#FSOUND_SIGNED.l = $00000100
#FSOUND_MONO.l = $00000020

#FSOUND_FREE.l = -1

; Buffer Structure

#BufferSize.l = 1000 ; 1000 word (2000 bytes)

Structure BufferType
  Sample.w[#BufferSize]
EndStructure

; Callback

Procedure.b MyStreamCallback(*Stream, *Buff.BufferType, Len.l, Param.l)
  For Index.l = 0 To #BufferSize-1
    *Buff\Sample[Index] = 0
  Next
  ProcedureResult = #true
EndProcedure

; Open a console, and play the stream until ENTER is pressed

OpenConsole()

PrintN("Initializing FMOD...")

FSOUND_Init(44100, 32, 0)

MyStream = FSOUND_Stream_Create(@MyStreamCallback, #BufferSize*2, #FSOUND_16BITS | #FSOUND_SIGNED | #FSOUND_MONO, 44100, 1)

If MyStream=0
  PrintN("Stream creation failed!")
Else
  FSOUND_Stream_Play(#FSOUND_FREE, MyStream)
EndIf

Print("Press ENTER to quit")
Input()

FSOUND_Stream_Close(MyStream)

FSOUND_Close()

CloseConsole()
BackupUser
PureBasic Guru
PureBasic Guru
Posts: 16777133
Joined: Tue Apr 22, 2003 7:42 pm

Post by BackupUser »

Restored from previous forum. Originally posted by Danilo.

Pointer-problem:
You used @MyStreamCallback instead @MyStreamCallback()

Code: Select all

FSOUND_Init(44100, 32, 0)MyStream = FSOUND_Stream_Create(@MyStreamCallback(), #BufferSize*2, #FSOUND_16BITS | #FSOUND_SIGNED | #FSOUND_MONO, 44100, 1)

If MyStream=0
cya,
...Danilo
(registered PureBasic user)
BackupUser
PureBasic Guru
PureBasic Guru
Posts: 16777133
Joined: Tue Apr 22, 2003 7:42 pm

Post by BackupUser »

Restored from previous forum. Originally posted by mindplay.

thanks, Danilo! that solved it! :D

there were some other problems, but I solved those - here's the final code, which produces a 440 Hz test tone ... this should make a nice example for others to start from? it would have helped me a lot if something like this came with the PB examples for FMOD - who's the author? I'd like to ask him to include this, might save others some trouble :)

Code: Select all

; FMOD Constants

#FSOUND_16BITS.l = $00000010
#FSOUND_SIGNED.l = $00000100
#FSOUND_MONO.l = $00000020

#FSOUND_FREE.l = -1

; Buffer Structure

#BufferSize.l = 4096*4 ; buffer size (increase if the sound breaks up!)

Structure BufferType
  Sample.w[#BufferSize]
EndStructure

; Callback

SinPos.f = 0

Procedure.b MyStreamCallback(*Stream.l, *Buff.BufferType, Len.l, Param.l)
  Global SinPos
  For Index.l = 0 To (Len/2)-1
    SinPos = SinPos + (440/44100)
    If SinPos>1
      SinPos - 1
    EndIf
    *Buff\Sample[Index] = Int(Sin(SinPos*3.1415*2)*30000)
  Next
  ProcedureReturn 1 ; must return 1 to continue playing
EndProcedure

; Open a console, and play the stream until ENTER is pressed

OpenConsole()

PrintN("Initializing FMOD...")

FSOUND_Init(44100, 32, 0)

MyStream = FSOUND_Stream_Create(@MyStreamCallback(), #BufferSize*2, #FSOUND_16BITS | #FSOUND_SIGNED | #FSOUND_MONO, 44100, 1)

If MyStream=0
  PrintN("Stream creation failed!")
Else
  FSOUND_Stream_Play(#FSOUND_FREE, MyStream)
  PrintN("Playing Stream")
EndIf

Print("Press ENTER to quit")
Input()

FSOUND_Stream_Close(MyStream)

FSOUND_Close()

CloseConsole()

BackupUser
PureBasic Guru
PureBasic Guru
Posts: 16777133
Joined: Tue Apr 22, 2003 7:42 pm

Post by BackupUser »

Restored from previous forum. Originally posted by Danilo.

> it would have helped me a lot if something like this came with
> the PB examples for FMOD

> I'd like to ask him to include this, might save others some trouble :)

OK, thanks - i´ll include it :)

If you have more examples you want included,
just tell me.

cya,
...Danilo
(registered PureBasic user)
BackupUser
PureBasic Guru
PureBasic Guru
Posts: 16777133
Joined: Tue Apr 22, 2003 7:42 pm

Post by BackupUser »

Restored from previous forum. Originally posted by mindplay.

If you'd like to include a slightly more complicated example, here's a tiny bassline synthesizer/sequencer I wrote today - it's just a 1-pol filter on a shaped sinustone with a linear envelope, but it might make some people curious and get them started experimenting with their own synthesizer techniques :)

Code: Select all

; FMOD Constants

#FSOUND_16BITS.l = $00000010
#FSOUND_SIGNED.l = $00000100
#FSOUND_MONO.l = $00000020

#FSOUND_FREE.l = -1

; Music/math constants and utility functions

#SampleRate = 44100

#Pi.f = 3.14159265
#Pi2.f = 6.2831853

Procedure.f NoteFreq(note.f)
  ProcedureReturn Pow(2, (note+11)/12)*8.1757989
EndProcedure

#NumSteps = 8

Structure StepStruct
  Freq.f
EndStructure

Dim Seq.StepStruct(#NumSteps)

StepNum.l = 0
NoteDur.l = #SampleRate/(130*4/60) ; 130 BPM, 4 steps per beat
NotePos.l = 0

Seq(0)\Freq = NoteFreq(36)
Seq(1)\Freq = NoteFreq(48)
Seq(2)\Freq = NoteFreq(42)
Seq(3)\Freq = NoteFreq(30)
Seq(4)\Freq = NoteFreq(48)
Seq(5)\Freq = NoteFreq(47)
Seq(6)\Freq = NoteFreq(46)
Seq(7)\Freq = NoteFreq(30)

; Buffer Structure

#BufferSize.l = 4096*4 ; buffer size (increase if the sound breaks up!)

Structure BufferType
  Sample.w[#BufferSize]
EndStructure

; Filter Structure

#Filter_HiCutoff = 1200

Structure FilterStruct
  rlpf_f.f ; cutoff frequency
  rlpf_r.f ; resonance amount
  rlpf_c.f
  rlpf_pos.f
  rlpf_speed.f
EndStructure

DefType.FilterStruct Filter

Filter\rlpf_pos=0
Filter\rlpf_speed=0
Filter\rlpf_r=0.995
Filter\rlpf_f=#Filter_HiCutoff
Filter\rlpf_c=2-2*Cos(2*pi*rlpf_f/#SampleRate)

; Callback

SinPos.f = 0

Procedure.b MyStreamCallback(*Stream.l, *Buff.BufferType, Len.l, Param.l)
  Global SinPos, NotePos, NoteDur, StepNum, Seq, Filter
  For Index.l = 0 To (Len/2)-1
    ; Waveform
    SinPos = SinPos + ((Seq(StepNum)\Freq)/#SampleRate)
    If SinPos>1
      SinPos - 1
    EndIf
    
    ; Oscillator
    Smp.f = Sin(SinPos * #Pi2)
    
    ; Shaping/distortion (sinus towards square)
    Smp = Smp / (Abs(Smp)+0.01)
    
    ; Simple envelope
    Smp = Smp * ((NoteDur-NotePos)/NoteDur)

    ; Filter
    Filter\rlpf_speed = Filter\rlpf_speed + (Smp - Filter\rlpf_pos) * Filter\rlpf_c
    Filter\rlpf_pos = Filter\rlpf_pos + Filter\rlpf_speed
    Filter\rlpf_speed = Filter\rlpf_speed * Filter\rlpf_r
    Smp = Filter\rlpf_pos * 0.1

    Filter\rlpf_f = Filter\rlpf_f - 0.12
    Filter\rlpf_c = 2 - 2 * Cos(#Pi2 * Filter\rlpf_f / #SampleRate)

    ; Clipping
    If Smp>1
      Smp=1
    ElseIf SmpNoteDur
      ; Next Note
      NotePos=0
      StepNum+1
      SinPos=0
      If StepNum=#NumSteps
        StepNum=0
      EndIf
      ; Reset Filter Cutoff and recalculate coefficient
      Filter\rlpf_f=#Filter_HiCutoff
      Filter\rlpf_c = 2 - 2 * Cos(#Pi2 * Filter\rlpf_f / #SampleRate)
    EndIf
  Next
  ProcedureReturn 1 ; must return 1 to continue playing
EndProcedure

; Open a console, and play the stream until ENTER is pressed

OpenConsole()

PrintN("Simple Bassline Synthesizer/Sequencer example")
PrintN("Written by R. Schultz ([url]mailto:mp@mindplay.dk[/url])")
PrintN("Use freely, but give credit, and notify the author of any use!")
PrintN("")

PrintN("Initializing FMOD...")

FSOUND_Init(#SampleRate, 32, 0)

MyStream = FSOUND_Stream_Create(@MyStreamCallback(), #BufferSize*2, #FSOUND_16BITS | #FSOUND_SIGNED | #FSOUND_MONO, #SampleRate, 1)

If MyStream=0
  PrintN("Stream creation failed!")
Else
  FSOUND_Stream_Play(#FSOUND_FREE, MyStream)
  PrintN("Playing Stream")
EndIf

Print("Press ENTER to quit")
Input()

FSOUND_Stream_Close(MyStream)

FSOUND_Close()

CloseConsole()

BackupUser
PureBasic Guru
PureBasic Guru
Posts: 16777133
Joined: Tue Apr 22, 2003 7:42 pm

Post by BackupUser »

Restored from previous forum. Originally posted by mindplay.

And here's a new version with a synthesized bass drum too

Code: Select all

; FMOD Constants

#FSOUND_16BITS.l = $00000010
#FSOUND_SIGNED.l = $00000100
#FSOUND_MONO.l = $00000020

#FSOUND_FREE.l = -1

; Music/math constants and utility functions

#SampleRate = 44100

#Pi.f = 3.14159265
#Pi2.f = 6.2831853

Procedure.f NoteFreq(note.f)
  ProcedureReturn Pow(2, (note+11)/12)*8.1757989
EndProcedure

#NumSteps = 8

Structure StepStruct
  Freq.f
  Drum.l
EndStructure

Dim Seq.StepStruct(#NumSteps)

StepNum.l = 0
NoteDur.l = #SampleRate/(130*4/60) ; 130 BPM, 4 steps per beat
NotePos.l = 0

Seq(0)\Freq = NoteFreq(36)
Seq(1)\Freq = NoteFreq(48)
Seq(2)\Freq = NoteFreq(42)
Seq(3)\Freq = NoteFreq(30)
Seq(4)\Freq = NoteFreq(48)
Seq(5)\Freq = NoteFreq(47)
Seq(6)\Freq = NoteFreq(46)
Seq(7)\Freq = NoteFreq(30)

DrumEnv.f = 1
DrumPos.f = 0.5

Seq(0)\Drum = 1
Seq(1)\Drum = 0
Seq(2)\Drum = 0
Seq(3)\Drum = 0
Seq(4)\Drum = 1
Seq(5)\Drum = 0
Seq(6)\Drum = 0
Seq(7)\Drum = 0

; Buffer Structure

#BufferSize.l = 4096*4 ; buffer size (increase if the sound breaks up!)

Structure BufferType
  Sample.w[#BufferSize]
EndStructure

; Filter Structure

#Filter_HiCutoff = 1200

Structure FilterStruct
  rlpf_f.f ; cutoff frequency
  rlpf_r.f ; resonance amount
  rlpf_c.f
  rlpf_pos.f
  rlpf_speed.f
EndStructure

DefType.FilterStruct Filter

Filter\rlpf_pos=0
Filter\rlpf_speed=0
Filter\rlpf_r=0.995
Filter\rlpf_f=#Filter_HiCutoff
Filter\rlpf_c=2-2*Cos(2*pi*rlpf_f/#SampleRate)

; Callback

SinPos.f = 0

Procedure.b MyStreamCallback(*Stream.l, *Buff.BufferType, Len.l, Param.l)
  Global SinPos, NotePos, NoteDur, StepNum, Seq, Filter, DrumPos, DrumEnv
  For Index.l = 0 To (Len/2)-1
    ; Waveform
    SinPos = SinPos + ((Seq(StepNum)\Freq)/#SampleRate)
    If SinPos>1
      SinPos - 1
    EndIf
    
    ; Oscillator
    Smp.f = Sin(SinPos * #Pi2)
    
    ; Shaping/distortion (sinus towards square)
    Smp = Smp / (Abs(Smp)+0.01)
    
    ; Simple envelope
    Smp = Smp * ((NoteDur-NotePos)/NoteDur)

    ; Filter
    Filter\rlpf_speed = Filter\rlpf_speed + (Smp - Filter\rlpf_pos) * Filter\rlpf_c
    Filter\rlpf_pos = Filter\rlpf_pos + Filter\rlpf_speed
    Filter\rlpf_speed = Filter\rlpf_speed * Filter\rlpf_r
    Smp = Filter\rlpf_pos * 0.1

    Filter\rlpf_f = Filter\rlpf_f - 0.12
    Filter\rlpf_c = 2 - 2 * Cos(#Pi2 * Filter\rlpf_f / #SampleRate)

    ; Drum
    If Seq(StepNum)\Drum=1
      DrumPos = DrumPos + (130+DrumEnv*1500)/#SampleRate
      DrumEnv = DrumEnv * 0.9992
      Smp = Smp + ((NoteDur-NotePos)/NoteDur) * Sin(DrumPos)
    EndIf
    
    ; Volume down
    Smp = Smp * 0.7

    ; Clipping
    If Smp>1
      Smp=1
    ElseIf SmpNoteDur
      ; Next Note
      NotePos=0
      StepNum+1
      SinPos=0
      If StepNum=#NumSteps
        StepNum=0
      EndIf
      ; Reset Filter Cutoff and recalculate coefficient
      Filter\rlpf_f=#Filter_HiCutoff
      Filter\rlpf_c = 2 - 2 * Cos(#Pi2 * Filter\rlpf_f / #SampleRate)
      ; Reset Drum
      If Seq(StepNum)\Drum=1
        DrumPos = 0.5
        DrumEnv = 1
      EndIf
    EndIf
  Next
  ProcedureReturn 1 ; must return 1 to continue playing
EndProcedure

; Open a console, and play the stream until ENTER is pressed

OpenConsole()

PrintN("Simple Bassline Synthesizer/Sequencer example")
PrintN("Written by R. Schultz ([url]mailto:mp@mindplay.dk[/url])")
PrintN("Use freely, but give credit, and notify the author of any use!")
PrintN("")

PrintN("Initializing FMOD...")

FSOUND_Init(#SampleRate, 32, 0)

MyStream = FSOUND_Stream_Create(@MyStreamCallback(), #BufferSize*2, #FSOUND_16BITS | #FSOUND_SIGNED | #FSOUND_MONO, #SampleRate, 1)

If MyStream=0
  PrintN("Stream creation failed!")
Else
  FSOUND_Stream_Play(#FSOUND_FREE, MyStream)
  PrintN("Playing Stream")
EndIf

Print("Press ENTER to quit")
Input()

FSOUND_Stream_Close(MyStream)

FSOUND_Close()

CloseConsole()
this takes me back to the acidhouse days of the 80s - aciiied! woo! okay, enough of this sillyness ... :)
Post Reply