Hi. I know how to do exactly what you want pretty easily, apart from the fact that it comes out of the internal PC speaker / 'bleeper' rather than the soundcard.
Unfortunately though, for what you want, I only know how to do it a rather long-winded way - but hey, at least it gets the job done ;) (...eventually <_<)
For this I recommend using the
FMOD sound engine.
You can download it there (it's free), and it comes with quite a lot of examples for VB6 (I presume you're not using .NET...?).
This tutorial should get you a program which can produce (and change frequency of) a tone in realtime (don't need to make any WAV file). Since you say you're new to VB, I'll try to go in a pretty step-by-step fashion.
- Download and install the
FMOD Programmers' API.
- Start VB and choose to create a
Standard EXE.
The next thing you need to do is to save the project (yes, it's just a blank form so far). To keep everything neat and easy to keep track of, I suggest you put it in its own new folder.
- Wherever you save it, the next thing to do is copy
fmodex.dll (you'll find it where you installed FMOD, in the
api folder) and paste it into the folder which you saved the VB project in.
- Then, back in the FMOD program folder, in
api/vb copy those 4 VB Modules and paste them into the folder which your project's saved in
(gotta keep everything together, makes it so much easier to maintain).
- On your project on VB, in the
Project Explorer area (usually top-right - it's the bit where you'll see the forms, modules and other things used in your project), right-click, choose
Add, click
Module.
- On the
Add Module window, choose the
Existing tab and open one of the 4 Modules.
- Do the same to add the other 3 Modules so you end up with all 4 added to your project.
Now for the main coding part! >=D
- Double-click on your form so that you get to editing the
Form_Load() sub's code.
- Type this (or better still, copy-and-paste) :
OUTSIDE of the
Form_Load() sub (at the top of the code window, the
General Declarations area) :
-
Dim MainSystem As Long
-
Dim ToneChannel As Long
-
Dim DSPID as Long
-
INSIDE the Form_Load() sub:
-
Dim Result As FMOD_RESULT
-
Dim Version As Long
-
-
' Create our main system object and initialize. Its ID will be stored in the variable 'MainSystem', so we can refer to it later.
-
Result = FMOD_System_Create(MainSystem)
-
ERRCHECK (Result)
-
'Note that after every FMOD function, we'll tell VB to make sure that FMOD gave back no error (hence the ERRCHECK(Result))
-
-
'Find out what version of FMOD the DLL (fmodex.dll) is, store it in 'Version' variable
-
Result = FMOD_System_GetVersion(MainSystem, Version)
-
ERRCHECK (Result)
-
-
'FMOD_VERSION is a variable held in one of the 4 modules we added. Those modules were made for that specific version of FMOD.
-
'Therefore we have to make sure the DLL is of the same version, otherwise there could be incompatibilities (this is mainly for if you're gonna give the program to other people, because we need to make sure they have the same version of FMOD's DLL as the program needs)
-
If Version <> FMOD_VERSION Then
-
'If it's not the right version, show an error
-
MsgBox "Error! You are using an old Version of FMOD " & Hex$(Version) & ". " & _
-
"This program requires " & Hex$(FMOD_VERSION)
-
End If
-
-
'Initialize our system (MainSystem)!
-
Result = FMOD_System_Init(MainSystem, 2, FMOD_INIT_NORMAL, 0)
-
'MainSystem means that's the system we want to initialize, which we created earlier (yes, you can even use multiple 'systems' at once...)
-
'2 = the maximum number of channels which will be able to play on this system - i.e. we're telling it that only as many as 2 sounds need to be able to play at any one time here. I know you actually only need 1, but I like to use slightly more than necessary e.g. so I can tell if I accidentally tell a sound to play twice.
-
ERRCHECK (Result)
-
-
'Create a DSP which will make the tone which you want.
-
'A DSP is a 'unit' in FMOD which can either create sound from scratch (what we're using it for here - meaning that it's called a DSP oscillator), or to filter sound, e.g. bass boost or add a reverb)
-
'We're storing the ID of this DSP unit in the variable DSPID, which we can then use later to refer to this specific DSP unit, as we did with MainSystem.
-
Result = FMOD_System_CreateDSPByType(MainSystem, FMOD_DSP_TYPE_OSCILLATOR, DSPID)
-
ERRCHECK (Result)
-
-
'Set the frequency of our DSP oscillator
-
Result = FMOD_DSP_SetParameter(DSPID, FMOD_DSP_OSCILLATOR_RATE, 4000)
-
'That means 4,000 Hz
-
ERRCHECK (Result)
-
-
'Set the type of the to Square
-
Result = FMOD_DSP_SetParameter(DSPID, FMOD_DSP_OSCILLATOR_TYPE, 1)
-
ERRCHECK (Result)
-
'1 means Square waveform (sounds like the internal PC speaker). Also available are Sine wave (pure 'hum'), sawtooth, triangle, white noise (not in that order, and I can't remember what number means which. I thing 0 for Sine and 4 for WhiteNoise, but I can't remember off the top of my head. You can change it and see for yourself anyway if you need to.
-
-
-
'You play a sound on a channel - whether from a sound file or being made in realtime, like with this DSP unit.
-
'It is simpler to have the DSP permanently making a tone, and just pause/unpause the channel which it's playing on, than to actually keep stopping and starting the DSP itself.
-
'(Just trust me ;) )
-
'Start playing it on a channel. We've told FMOD that there should only be 2 channels available. FMOD will play the DSP on the first available channel, or if there is no channel available, the one which had something starting playing on it the longest time ago (therefore probably least important).
-
Result = FMOD_System_PlayDSP(System, FMOD_CHANNEL_REUSE, DSPID, 1, ToneChannel)
-
ERRCHECK (Result)
-
'The ID of the channel which it uses is stored in our variable ToneChannel.
-
'1 means that it WILL start paused. A 0 here would mean no, it will start producing sound straight away, not wait for us to unpause it.
-
-
'One more thing needs to be done to protect your precious speakers and ears - turn down the volume of that channel which will play the DSP! (Well, it already will be playing, just paused)
-
Result = FMOD_Channel_SetVolume(ToneChannel, 0.125)
-
ERRCHECK (Result)
-
'Volumes range from 0 to 1. So 0.125 may seem like a very low volume. However, the volumes are logarithmic, meaning that it makes more of a difference lower down, e.g. the difference between 0.1 and 0.2 is more obvious than 0.6 and 1.
-
You should see a sub called
ERRCHECK being called many times to check for and display any errors FMOD gave back for the last thing we told it to do. But that ERRCHECK function doesn't exist, so we need to make it.
Put this at the absolute bottom of the
code window (outside any subs)
-
Private Sub ERRCHECK(Result As FMOD_RESULT)
-
-
If Result <> FMOD_OK Then
-
MsgBox "FMOD error! (" & Result & ") " & FMOD_ErrorString(Result)
-
End If
-
-
End Sub
-
The next thing you must do, before we even try to hear the sound, is SAVE THE FORM before we crash VB and lose it!
It's very easy for VB to crash while we play around with FMOD.
If you forget to shut down the system (MainSystem) before exitting the program,VB will crash, simple as that.
- So next, on the code window, in the top-left pulldown menu choose
Form, and in the top-right menu choose
Unload.
We're gonna tell VB to shut down our MainSystem on FMOD whenever the form is about to close, to avoid VB crashing!
- In the Form_Unload() sub, copy/paste this:
-
If MainSystem Then
-
Result = FMOD_System_Close(System)
-
Result = FMOD_System_Release(System)
-
End If
-
Now you have to remember to never use the Stop button on VB (next to the Play button you click to run your program), unless you have saved, because it will crash because of us not shutting down MainSystem! However, if you exit the form as you'd have to when your program's made, it won't crash now.
Right, we're ready to try to hear the sound now!
- Create a CheckBox on the form and call it ToneCheck.
- Double-click it to get typing code in the
ToneCheck_Click() sub.
- Copy/paste this into that sub:
[code=vb]
If ToneCheck.Value = 1 Then
'The checkbox is ticked! We need to set ToneChannel's
paused state to 0 (not paused!)
'(In English: we need to unpause the channel so we can hear the tone!)
Result = FMOD_Channel_SetPaused(ToneChannel, 0)
ERRCHECK (Result)
'
0 = unpaused
Else
'Checkbox is unchecked - we don't want to hear the tone, pause ToneChannel!
Result = FMOD_Channel_SetPaused(ToneChannel, 1)
ERRCHECK (Result)
'
1 = paused
End If
Save now and run it, and check/uncheck the box. Hopefully this is what you need.
You can change the frequency of the DSP unit by using this code:
-
Result = FMOD_DSP_SetParameter(DSPID, FMOD_DSP_OSCILLATOR_RATE, YourNewFrequency)
-
ERRCHECK (Result)
-
I know that you wanted it to be so that you gave it the frequency and duration. Well here's a little sub which does that:
-
Public Sub FMODBeep(Frequency, Duration as Long)
-
Result = FMOD_DSP_SetParameter(DSPID, FMOD_DSP_OSCILLATOR_RATE, Frequency)
-
ERRCHECK(Result)
-
'That set the frequency to Frequency (Hz)
-
Result = FMOD_Channel_SetPaused(Channel, 0)
-
ERRCHECK (Result)
-
'That 'turned the tone on'
-
MsgWaitObj Duration
-
'That's making it wait for the Duration you gave (milliseconds)
-
-
Result = FMOD_Channel_SetPaused(Channel, 1)
-
ERRCHECK (Result)
-
'That's pausing the channel again after we've heard the tone for as long as we want to.
-
-
End Sub
-
There's a call to a sub there called
MsgWaitObj.
That makes VB wait there until a certain amount of time has passed, without maxing out the processor and without stopping the program from responding.
To be able to use it, add another Module (this time,
New), and paste this into it:
-
'-----
-
'INSANE COMPLEXNESS FOR NON-LOCKING SLEEP:
-
'START
-
'-----
-
'********************************************
-
'* (c) 1999-2000 Sergey Merzlikin *
-
'********************************************
-
-
Private Const STATUS_TIMEOUT = &H102&
-
Private Const INFINITE = -1& ' Infinite interval
-
Private Const QS_KEY = &H1&
-
Private Const QS_MOUSEMOVE = &H2&
-
Private Const QS_MOUSEBUTTON = &H4&
-
Private Const QS_POSTMESSAGE = &H8&
-
Private Const QS_TIMER = &H10&
-
Private Const QS_PAINT = &H20&
-
Private Const QS_SENDMESSAGE = &H40&
-
Private Const QS_HOTKEY = &H80&
-
Private Const QS_ALLINPUT = (QS_SENDMESSAGE Or QS_PAINT _
-
Or QS_TIMER Or QS_POSTMESSAGE Or QS_MOUSEBUTTON _
-
Or QS_MOUSEMOVE Or QS_HOTKEY Or QS_KEY)
-
Private Declare Function MsgWaitForMultipleObjects Lib "user32" _
-
(ByVal nCount As Long, pHandles As Long, _
-
ByVal fWaitAll As Long, ByVal dwMilliseconds _
-
As Long, ByVal dwWakeMask As Long) As Long
-
Private Declare Function GetTickCount Lib "kernel32" () As Long
-
'-----
-
'INSANE COMPLEXNESS FOR NON-LOCKING SLEEP:
-
'END
-
'-----
-
-
-
-
-
-
' The MsgWaitObj function replaces Sleep,
-
' WaitForSingleObject, WaitForMultipleObjects functions.
-
' Unlike these functions, it
-
' doesn't block thread messages processing.
-
' Using instead Sleep:
-
' MsgWaitObj dwMilliseconds
-
' Using instead WaitForSingleObject:
-
' retval = MsgWaitObj(dwMilliseconds, hObj, 1&)
-
' Using instead WaitForMultipleObjects:
-
' retval = MsgWaitObj(dwMilliseconds, hObj(0&), n),
-
' where n - wait objects quantity,
-
' hObj() - their handles array.
-
-
Public Function MsgWaitObj(Interval As Long, _
-
Optional hObj As Long = 0&, _
-
Optional nObj As Long = 0&) As Long
-
Dim T As Long, T1 As Long
-
If Interval <> INFINITE Then
-
T = GetTickCount()
-
On Error Resume Next
-
T = T + Interval
-
' Overflow prevention
-
If Err <> 0& Then
-
If T > 0& Then
-
T = ((T + &H80000000) _
-
+ Interval) + &H80000000
-
Else
-
T = ((T - &H80000000) _
-
+ Interval) - &H80000000
-
End If
-
End If
-
On Error GoTo 0
-
' T contains now absolute time of the end of interval
-
Else
-
T1 = INFINITE
-
End If
-
Do
-
If Interval <> INFINITE Then
-
T1 = GetTickCount()
-
On Error Resume Next
-
T1 = T - T1
-
' Overflow prevention
-
If Err <> 0& Then
-
If T > 0& Then
-
T1 = ((T + &H80000000) _
-
- (T1 - &H80000000))
-
Else
-
T1 = ((T - &H80000000) _
-
- (T1 + &H80000000))
-
End If
-
End If
-
On Error GoTo 0
-
' T1 contains now the remaining interval part
-
If IIf((T1 Xor Interval) > 0&, _
-
T1 > Interval, T1 < 0&) Then
-
' Interval expired
-
' during DoEvents
-
MsgWaitObj = STATUS_TIMEOUT
-
Exit Function
-
End If
-
End If
-
' Wait for event, interval expiration
-
' or message appearance in thread queue
-
MsgWaitObj = MsgWaitForMultipleObjects(nObj, _
-
hObj, 0&, T1, QS_ALLINPUT)
-
' Let's message be processed
-
DoEvents
-
If MsgWaitObj <> nObj Then Exit Function
-
' It was message - continue to wait
-
Loop
-
End Function
-
Sorry for the absolutely ridiculous length of this post!
Good luck~!
If you have any more questions, please ask! ^^