Ich bin gerade dabei einen kleinen Synthesizer zu basteln, den man
irgendwann dann auch mal komfortabel in kleine PureBasic-Projekte
einbinden kann und ein bisschen Musik machen kann.
Den kompletten Quellcode gibt es jetzt noch nicht, weil das ganze noch
lange nicht fertig ist. Aber ein kleiner Vorgeschmack als EXE-Datei und
externer fmodex.dll gibt es hier:
- fmodex.dll
- MSS.exe
Einfach beides in einen Ordner herunterladen und starten. Es öffnet sich
ein Mausklavier, ein Debug-Fenster ohne Debug-Informationen und ein
schwarzes Fenster. Beim Drücken einer Taste sollte dann ein Ton ertönen
und im scharzen Fenster die Welle angezeigt werden. Mit der Tastatur geht
es übrigens auch.
Das Signal ist ein Rechtecksignal (Square), das mit einem Dreiecksignal
(Triangle) amplitudenmoduliert und einem Sinussignal (Sine)
frequenzmoduliert ist. Die Frequenz der Amplitudenmodulation liegt bei
einem Hertz. Die Frequenz der Frequenzmodulation liegt bei 3 Hertz und
ihre Amplitude ist abhängig von der Frequenz des Rechtecksignals mit dem
Multiplikator 0,05.
Soviel zu dem Klang. Hier noch ein Code-Ausschnitt der Main.pb des
Programmes.
Code: Alles auswählen
Procedure AmplitudeCallback(time.d, Amplitude.d, Velocity.d, *result.Double)
  
EndProcedure
Procedure.d FrequencyCallback(time.d, Frequency.d, inFrequency.d, Velocity.d)
  
EndProcedure
Procedure EnumMixerInputs(*Wave.MSS_WaveForm)
  ProcedureReturn #True
EndProcedure
Macro Quote
  "
EndMacro
Macro DebugVar(a)
  Debug Quote#a#Quote + ": " + Str(a)
EndMacro
Macro OK(a)
  Debug Quote#a#Quote
  Result = a
  If Result
    Debug FMOD_ErrorString(Result)
    End
  EndIf
EndMacro
Define *IS.ImageScreen, *Keys.Keys
Define Width.l = 1000, Height.l = 400
Define Samplerate.l, Channels.l, exinfo.FMOD_CREATESOUNDEXINFO
Global Dim OnNotes.b(127), Dim NoteSample.l(127), Dim *RefreshOut(127), Dim *FMODOut(127)
Global *Wave.MSS_WaveForm, *Mix.MSS_Mixer
Samplerate = 44100
Channels = 1
Procedure KeyCallback(*Keys.Keys, Note.b, Velocity.b, UserData.l)
  OnNotes(Note) = Velocity
  NoteSample(Note) = 0
EndProcedure
DataSection ;{ Tasten
  Tasten:
    Data.l 25 ;Anzahl aufeinander folgender Noten
    Data.l #VK_Y, #VK_S, #VK_X, #VK_D, #VK_C, #VK_V, #VK_G, #VK_B, #VK_H, #VK_N, #VK_J, #VK_M
    Data.l #VK_Q, #VK_2, #VK_W, #VK_3, #VK_E, #VK_R, #VK_5, #VK_T, #VK_6, #VK_Z, #VK_7, #VK_U, #VK_I
    Data.l -1, -1  ;eins höher/tiefer (-1 = ignorieren)
    Data.l #VK_UP, #VK_DOWN ;eine oktave höher/tiefer
EndDataSection ;}
Procedure RefreshWindow(*IS.ImageScreen)
  Protected x.l, Width.l = *IS\Width(), Height.l = *IS\Height(), yoff.l = Height / 2, oldy.l = yoff, y.d, a.l
  Protected Input.MSS_Input
  Static *Out
  
  If *Out = 0 : *Out = *Wave\NewOut() : EndIf
  
  *IS\StartDraw()
    Box(0, 0, Width, Height, 0)
    For xl = 0 To Width
      y = 0
      For a = 0 To 127
        If OnNotes(a) > 0
          Input\Velocity = OnNotes(a) / 127
          Input\Frequency = 220 * Pow(2, (a - 68) / 12)
          Input\sample = xl * 44100 / Width
          y + *Wave\Out(@Input, *RefreshOut(a))
        EndIf
      Next
      y * -yoff + yoff
      LineXY(xl - 1, oldy, xl, y, $FFFFFF)
      oldy = y
    Next
  *IS\StopDraw()
  
  *IS\ShowChanges()
EndProcedure
Procedure.l Buffercallback(*Sound, *BufferPointer, length.l)
  Protected float.f, *sample.Float, *sample_last, mpos.POINT, a.l
  Protected Input.MSS_Input
  Static *Out
  Shared Channels.l, Samplerate.l
  *sample_last = length + *BufferPointer
  *sample = *BufferPointer
  
  If *Out = 0 : *Out = *Wave\NewOut() : EndIf
  
  Repeat
    float = 0
    For a = 0 To 127
      If OnNotes(a) > 0
        Input\Velocity = OnNotes(a) / 127
        Input\Frequency = 440 * Pow(2, (a - 68) / 12)
        Input\sample = NoteSample(a)
        float + *Wave\Out(@Input, *FMODOut(a))
        NoteSample(a) + 1
      EndIf
    Next
    
    For offset = 1 To Channels
      *sample\f = float
      
      *sample + SizeOf(Float)
    Next
  Until *sample => *sample_last
  
  ProcedureReturn #FMOD_OK
EndProcedure
;{- Wave-Fenster und Keyboard
*IS = IS_New()
*IS\Open(0, 0, Width, Height, "ModuleSoundSystem", 0, #PB_Window_SystemMenu | #PB_Window_ScreenCentered)
*Keys = Keys_New("Mausklavier")
*Keys\Resize(300, 0, 600, 40)
*Keys\AutoAspectRatio(#True)
*Keys\Hide(#False)
*Keys\Callback(@KeyCallback(), 0)
*Keys\SyncInput(#True)
*Keys\SetKeyboard(?Tasten)
*Keys\KeyNote(60)
;}
;{- Waveformen
Define.MSS_WaveForm *Wave1a, *Wave1b, *Wave1, *Wave4
Define.MSS_Mixer *Mix1, *Mix2
*Wave1a = MSS_WaveForm_New()
*Wave1a\Type(#MSS_WaveForm_Sine) ;Sinussignal
*Wave1a\Samplerate(Samplerate)
*Wave1a\Frequency(3) ;Frequenz
*Wave1a\FrequencyType(#MSS_Frequency_Static) ;Frequenz mit Eingangsfrequenz multiplizieren 
*Wave1a\Amplitude(0.05) ;Amplitudenhöhe
*Wave1a\AmplitudeType(#MSS_Amplitude_FrequencyMult) ;Amplitudenhöhe mit Eingangsfrequenz multiplizieren
DebugVar(*Wave1a)
*Wave1b = MSS_WaveForm_New()
*Wave1b\Type(#MSS_WaveForm_Triangle) ;Sinussignal
*Wave1b\Samplerate(Samplerate)
*Wave1b\Frequency(1) ;Frequenz
*Wave1b\FrequencyType(#MSS_Frequency_Static) ;Frequenz multiplizieren mit Eingangsfrequenz
*Wave1b\Amplitude(1) ;Amplitudenhöhe
*Wave1b\AmplitudeType(#MSS_Amplitude_Static) ;Amplitudenhöhe mit Eingangslautstärke multiplizieren
*Wave1b\Phase(0.75)
DebugVar(*Wave1b)
*Wave1 = MSS_WaveForm_New()
*Wave1\Type(#MSS_WaveForm_Square) ;Sinussignal
*Wave1\Samplerate(Samplerate)
*Wave1\Frequency(1) ;Frequenz
*Wave1\FrequencyType(#MSS_Frequency_WaveFormAddI) ;Waveform addieren zu Eingangsfrequenz
*Wave1\SetFrequencyCallback(*Wave1a)
*Wave1\Amplitude(0.25) ;Amplitudenhöhe
*Wave1\AmplitudeType(#MSS_Amplitude_WaveformAddVxA) ;Amplitudenhöhe mit Eingangslautstärke multiplizieren
*Wave1\SetAmplitudeCallback(*Wave1b)
DebugVar(*Wave1)
; *Mix1 = MSS_Mixer_New()
; *Mix1\Type(#MSS_Mixing_Average) ;Durchschnittswert aus allen Eingängen nehmen
; *Mix1\Voices(128)
; ;*Mix1\Volume(0.25)
; ;*Mix1\AddInput(*Wave1b, #MSS_Input_WaveForm)
; *Mix1\AddInput(*Wave2, #MSS_Input_WaveForm)
; ;*Mix1\AddInput(*Wave3, #MSS_Input_WaveForm)
; *Mix1\AddInput(*Wave4, #MSS_Input_WaveForm)
; DebugVar(*Mix1)
*Wave = *Wave1
;}
Define a.l
For a = 0 To 127
  *RefreshOut(a) = *Wave\NewOut()
  *FMODOut(a) = *Wave\NewOut()
Next
;{ FMOD initialisieren und Sound starten
Init_FMOD()
OK(FMOD_System_Create(@*System)) ;System erstellen
OK(FMOD_System_Init(*System, Channels, #FMOD_INIT_NORMAL, 0)) ;System initialisieren
With exinfo
  \cbSize = SizeOf(FMOD_CREATESOUNDEXINFO)
  \Numchannels = Channels                     ;Anzahl Kanäle (Stereo)
  \defaultfrequency = Samplerate              ;Die Samplerate (44100)
  \Format = #FMOD_SOUND_FORMAT_PCMFLOAT       ;Das Format (Float)
  \length = Samplerate                        ;Die Länge des Samples (1 Sekunde)
  \decodebuffersize = Samplerate / 20         ;Die Anzahl an Samples pro Callback-Aufruf (25 ms)
  \pcmreadcallback = @Buffercallback()        ;Der Pointer zum Callback
EndWith
;Sample erstellen (2D-Sound, Software-Rendering, Loop)
OK(FMOD_System_CreateSound(*System, 0, #FMOD_2D | #FMOD_OPENUSER | #FMOD_SOFTWARE | #FMOD_CREATESTREAM | #FMOD_LOOP_NORMAL, @exinfo, @*Sound))
;Sound abspielen
OK(FMOD_System_PlaySound(*System, #FMOD_CHANNEL_FREE, *Sound, #False, @*channel))
;}
Repeat
  EventID = WindowEvent()
  *Keys\Event(EventID)
  Select EventID
    Case #PB_Event_CloseWindow
      Break
    
    Case 0
      RefreshWindow(*IS)
      Delay(10)
  EndSelect
  FMOD_System_Update(*System)
ForEver
;{ Sound stoppen und FMOD deinitialisieren
OK(FMOD_Channel_Stop(*channel))
OK(FMOD_Sound_Release(*Sound))
OK(FMOD_System_Release(*System))
*IS\Kill()
*Wave1\Kill()
End
;}
