Tek-Tips is the largest IT community on the Internet today!

Members share and learn making Tek-Tips Forums the best source of peer-reviewed technical information on the Internet!

  • Congratulations Chriss Miller on being selected by the Tek-Tips community for having the most helpful posts in the forums last week. Way to Go!

midi code from within vb 2

Status
Not open for further replies.

chals1

Technical User
Sep 3, 2003
73
ES
How could i play a midi note from within a program
withot playing any midi file at all ?
I could use the winapi BEEP but it doesn't work on W98 machines


 
>I could use the winapi BEEP but it doesn't work on W98 machines

There are alternatives which allow you to emulate the Beep API correctly on Win98.

See my last two posts at the bottom of the thread222-53432.

Note that the download like to file win95io.zip is changed. The new download link is as under.
 
Thank you, this is a way out but is it possible to play a midi string written inside the code?
 
Very cute, Hypetia. :) You got me to play around. These values
Code:
Frequencies = "afhihfhimrrqrtuqrqrtuqrwyzywyz"
Durations = "diaaaaacdebbbbbfbbbbbgdhaaaaad"

Duration = (Asc(Mid$(Durations, Note, 1)) - 96) * 200 - 10
represent an attempt at something more musically ambitious, quickly demonstrating the limitations of the technique for serious composition. Of course, it wasn't intended for that, and works just fine as a glorified Beep mechanism.

Bob
 
Very cute, Hypetia. :) You got me to play around. These values
Code:
Frequencies = "afhihfhimrrqrtuqrqrtuqrwyzywyz"
Durations = "diaaaaacdebbbbbfbbbbbgdhaaaaad"

Duration = (Asc(Mid$(Durations, Note, 1)) - 96) * 200 - 10
represent an attempt at something more musically ambitious, quickly demonstrating the limitations of the technique for serious composition (the music of John Denver excepted, of course). Of course, it wasn't intended for that, and works just fine as a glorified Beep mechanism.

Bob
 
Hi Hypetia,

Fine!!
Could you explain that algoritm?
How Does the for bucle work?
 
Sorry for the double post.

Well, the Beep function is in terms of frequency and duration. Frequency is in terms of cycles per second, duration is in terms of milliseconds. (I goofed in my post; I changed the 200 to a 100 to speed up the tempo.)

Ok, here is some background about music. Musical pitch is determined by the frequency, or cycles per second, of a note. One cycle per second is also one Hertz, or Hz. You will usually see frequency values expressed in terms of Hz.

Musical instruments are tuned such that one of their notes is at an agreed-upon frequency; modern orchestras tune the A above middle C to 440 Hz. This is termed "A-440 tuning." Hypetia's E4 constant is analogous to this idea. If you changed the constant to 440 you would find that the whole piece sounded higher.

Next, notes that are an octave apart from one another are in a frequency ratio of 2 to 1. So, the A an octave higher than A-440 has a frequency of 880 Hz, while the one an octave lower is at 220 Hz. In western music, there are twelve equally-spaced notes in an octave. Each of these is called a "half tone", and a run of all twelve notes in succession is called a "chromatic scale." Hypetia's program expresses the notes of the chromatic scale in terms of ASCII letters, where the first note is a, the next is b, and so on. A chromatic scale, from the lowest note available to the one an octave higher, would be expressed by the string "abcdefghijklm", where m is the note an octave higher than a.

Now, let's break down the formula:
Code:
Frequency = E4 * 2 ^ ((Asc(Mid$(Frequencies, Note, 1)) - 96) / 12)
First, let's look at this:
Code:
Mid$(Frequencies, Note, 1
Inside the for next loop, this pulls the characters out of the Frequencies string variable one at a time. So, if Frequencies = "abcdefghijkl", it will pull out an a the first time through the loop, a b the next time, and so on. So, let's substitute an a in the code for the Mid$ formula:
Code:
Frequency = E4 * 2 ^ ((Asc("a")) - 96) / 12)
Note that asc("a") is 97. So this code boils down to
Code:
Frequency = E4 * 2 ^ (1/12)
Now, if there were a b in the original string, that would boil down to 2^(2/12), a c would be 2^(3/12) and so on. An l would be 2^(12/12), which of course equals 2. This means that l would multiply E4 by 2, and would therefore be an octave higher than the constant.

So, the number 96 in Hypetia's formula is because the ascii value for "a" is 97, and the number 12 is because there are 12 notes in a western musical scale.

As for duration, lowering the number 200 in the formula will speed up the tempo, raising it will slow down the tempo.

Bob

Bob
 
Thanks Bob, for explaining my code. I give you a star for this excellent explanation. You saved a lot of my time.

chals1, as far as midi is concerned, I never generated midi notes using VB6 code. In fact, I have no idea how to do it. Perhaps it would be possible using Microsoft DirectMusic.

However, I did play with WAVEFORM audio and wrote some code that generated wave sound on-the-fly and played using sound card.

The idea is to compose a wave buffer in the code and pass the waveform data to PlaySound API function with SND_MEMORY flag.

Although this method works, this is not very practical as composing the sound data in realtime is a great overhead to your program. However, there is no harm doing some experiment.

So, here is my second version of Beep function (first goes in thread222-53432 for Win98) which plays the sound from sound card instead of motherboard speaker.
___
[tt]
Option Explicit
Private Declare Function PlaySound Lib "winmm" Alias "PlaySoundA" (lpszName As Any, ByVal hModule As Long, ByVal dwFlags As Long) As Long
Private Declare Sub CopyMemory Lib "kernel32" Alias "RtlMoveMemory" (Destination As Any, Source As Any, ByVal Length As Long)
Private Declare Sub Sleep Lib "kernel32" (ByVal dwMilliseconds As Long)
Const SND_SYNC = &H0
Const SND_MEMORY = &H4
Const WAVE_FORMAT_PCM = 1
Private Type WAVEFORMHEADER
'riff chunk begins here
RIFF_Chunk As String * 4
RIFF_Size As Long
'riff data follows
RIFF_Type As String * 4 'WAVE'
'fmt chunk begins here
fmt_Chunk As String * 4 'sub chunk 'fmt '
fmt_Size As Long
'fmt data follows which is
'a WAVEFORMAT structure
'Type WAVEFORMAT
wFormatTag As Integer
nChannels As Integer
nSamplesPerSec As Long
nAvgBytesPerSec As Long
nBlockAlign As Integer
nBitsPerSample As Integer 'Specific to WAVE_FORMAT_PCM
'End Type
'fmt chunk ends here
data_Chunk As String * 4 'sub chunk 'data'
data_Size As Long
End Type
'sound data follows till the end of file
'riff chunk ends with the end of file

Sub Beep(Frequency As Long, Duration As Long)
Dim wfh As WAVEFORMHEADER
Const Pi = 3.1415927
wfh.RIFF_Chunk = "RIFF"
'size of RIFF chunk will be computed later.
wfh.RIFF_Type = "WAVE"
'fmt chunk begins
wfh.fmt_Chunk = "fmt "
wfh.fmt_Size = 16 '2+2+4+4+2+2
'fmt data begins
wfh.wFormatTag = WAVE_FORMAT_PCM 'PCM format
wfh.nChannels = 1 'mono
wfh.nSamplesPerSec = 22050 '22.05 KHz
wfh.nBitsPerSample = 8 '1 byte / sample
wfh.nBlockAlign = wfh.nChannels * wfh.nBitsPerSample / 8
wfh.nAvgBytesPerSec = wfh.nSamplesPerSec * wfh.nChannels * wfh.nBitsPerSample / 8
'fmt chunk ends
'data chunk begins here
wfh.data_Chunk = "data"
wfh.data_Size = wfh.nAvgBytesPerSec * Duration / 1000
wfh.RIFF_Size = Len(wfh) + wfh.data_Size 'header + data
'data samples begin
Dim B() As Byte, N As Long
ReDim B(-Len(wfh) To wfh.data_Size - 1)
'compose the waveform data using for loop
For N = 0 To wfh.data_Size - 1
B(N) = 127 + 127 * Sin(Frequency / wfh.nSamplesPerSec * 2 * Pi * N)
Next
'data chunk ends
'copy the wave header (44 bytes) to the start of the array
CopyMemory B(-Len(wfh)), wfh, Len(wfh)
PlaySound B(-Len(wfh)), 0, SND_MEMORY Or SND_SYNC
End Sub
'"Annie's Song"
' John Denver
Private Sub Form_Load()
Dim Note As Long
Dim Frequencies As String, Durations As String
Dim Frequency As Long, Duration As Long
Frequencies = "iiihfihfffhidadddfhihfffhihiiihfihffihfdadddfhihffhiki"
Durations = "aabbbfjaabbbbnaabbbfjaabcapaabbbfjaabbbbnaabbbfjaabcap"
Const E4 = 329.6276
For Note = 1 To Len(Frequencies)
Frequency = E4 * 2 ^ ((Asc(Mid$(Frequencies, Note, 1)) - 96) / 12)
Duration = (Asc(Mid$(Durations, Note, 1)) - 96) * 200
Beep Frequency, Duration
DoEvents
Next
Unload Me
End Sub[/tt]
___

Note that in above code the array that holds the waveform data has an LBound < 0. The array is declared from -Len(wfh) to a positive value. The negative portion of the array (-44 to -1) holds the waveform header where as the positive portion (0 to onwards) holds the actual sound data. I did this arrangement for making the For loop easy to traverse.

Using waveforms for producing sound is harder but highly flexible. You can control every aspect of the sound because you have direct access to data bytes sent to D/A converter of your sound card.

Consider the following scenario.

In the above code, the body of For loop that composes the sound data is.
[tt]
B(N) = 127 + 127 * Sin(Frequency / wfh.nSamplesPerSec * 2 * Pi * N)
[/tt]
Now modify this code as follows.
[tt]
If N < wfh.nSamplesPerSec Then
B(N) = 127 + 127 * Sin(Frequency / wfh.nSamplesPerSec * 2 * Pi * N) * Exp(-(N) / wfh.nSamplesPerSec * 5)
Else
B(N) = 127
End If
[/tt]
Run the code again. You will notice that the Exp function causes the sine wave to decay exponentially. This effect lasts for 1 second after which the amplitude of the sine wave is less than Exp(-5) = 0.0067 = 0.67%. After 1 second, the waveform is considered fully decayed and is set to 127.

The overall effect of this modification is that it makes the sound more natural and pleasing to hear.

You can even construct entire ADSR envelopes using this method, but it will be very difficult to do all that stuff in realtime at it demands a lot of processor time, which is not practical.

Hope it helps.
 
The WMP plays MIDI files. Use this code:

Code:
Option Explicit
' Add the Windows Media Player control to your app

Private Sub Form_Activate()
  Debug.Print wmp.currentMedia.durationString
End Sub

Private Sub Form_Load()
'  wmp.URL = App.Path & "\Demo.mp3"
  wmp.URL = App.Path & "\amazingr.mid"
End Sub

David
 
<You can even construct entire ADSR envelopes using this method

In other words, you've constructed the beginnings of an "envelope shaper." Very interesting. Although anyone using a synthesizer has used one, I never had any idea how to create one. A star for you too!

To expand on Hypetia's statement: a formal envelope shaper definition has four components: attack, decay, sustain, release. Each aspect will alter amplitude in a different way.

To explain this a little more clearly, one may use a real life analogue, say the plucking of a guitar string. When you pluck a guitar string, you pull it and let it go. It then vibrates for a while and stops. When you pull it and let it go, you pull it further out of line than it is while it's vibrating.

So, attack is the increase in amplitude caused by the initial plucking of the string. Decay is the initial lessening of amplitude between the time that the inertial pull that you have created on the string balances the tendency of the string to go back to rest. At the time that balance is achieved, we have sustain. The string vibrates for a while. Then the weight of the string and gravity take over, and the amplitude falls off more rapidly than it did in the period of sustain.

(If you try to imagine these values when drawing a bow across a violin string, you'll get an idea of why synthesized string sounds have been much slower to sound real than synthesized guitar sounds!)

So, an envelope shaper allows the setting of rate of dropoff (rate of increase in the case of attack) of these four aspects of a given wave form's occurrence in time.

Hypetia's envelope has a piano-like sound, which is not surprising. A piano's strings are extremely tight (a grand piano's harp bears stresses of several tons), so the attack is pretty much instantaneous, with very little decay. Also, the release in a piano sound is pretty much an exponential dropoff. The thing that's missing is the sustain capability on longer notes.

So. Hypetia, I fooled around a bit with some things to add in a sustain capability for notes that are longer than the samples per second value. However, I don't quite get how the formula
Code:
B(N) = 127 + 127 * Sin(Frequency / wfh.nSamplesPerSec * 2 * Pi * N)
works, probably because my understanding of sines is "opposite over hypotenuse" and dates back 30 years or so. In particular, I don't understand why your formula starts with an increase in amplitude for the first 10 values and then begins to decrease, which messes up all my attempts. Can you explain this a bit?

Thanks,

Bob
 
Bob, you figured out more complex formulas related to frequency and duration -- this one is much simple.[wink]

Given a frequency f, the function f(t) = Sin(2 * Pi * f * t) gives a time-varying sine wave of frequency f and amplitude -1 to +1. Same concept applies with:

B(N) = 127 + 127 * Sin(Frequency / wfh.nSamplesPerSec * 2 * Pi * N)

The above equation cab be worked out in two steps.

B(N) = 127 + 127 * Sin(X),
having X = Frequency / wfh.nSamplesPerSec * 2 * Pi

We know that Sin function gives a value in the range -1 to +1, so 127 + 127 * Sin(X) scales the sine wave to between 0 to 254 which fits just right in a Byte, which is the size of one sample (wfh.nBitsPerSample = 8).

The mean-value of this sample is 127 which means no amplitude or complete silence. As the sample values oscillate above and below this mid range, they produce sound.

Next comes the argument of the Sin function -- X.

X = Frequency / wfh.nSamplesPerSec * 2 * Pi * N
X = 2 * Pi * Frequency * N / wfh.nSamplesPerSec

Here N is the For loop counter and holds the current sample number among all samples, so the above equation becomes:

X = 2 * Pi * Frequency * CurrentSample / (NumberOfSamples/Second)
X = 2 * Pi * Frequency * CurrentTime
X = 2 * Pi * f * t

Which reduces to the same sine function Sin(2 * Pi * f * t) as described in this beginning of the post. It means that

B(N) = 127 + 127 * Sin(Frequency / wfh.nSamplesPerSec * 2 * Pi * N)

creates sample values in such a way that it produces a Sine wave of argument frequency and amplitude 0-254.

When Exp() function multiplies with Sin function, it damps the sinusoidal oscillations and eventually all the samples are eventually decayed to 0 amplitude after some time (values approaching 127). The (-) sign in the argument of Exp() function makes it a decaying function, which has no effect when N is small, but decays rapidly as N grows.
 
<Given a frequency f, the function f(t) = Sin(2 * Pi * f * t) gives a time-varying sine wave of frequency f and amplitude -1 to +1.

That's what I wasn't getting. Thanks Hypetia.

Now, what I tried doing with your code was to take any note that was longer than 1 second, and only start applying the Exp() function when the note had only one second left. I thought I might get a rudimentary sustain from this, but the results were unsatisfactory--it sounded like there were 2 notes.

Any insights? If you like, I'll run up the code again and post it.

Bob
 
Hey!
This could be useful to process sound streams on realtime, like the online radio ones
How could i capture sound, and process it on real time ?
I'm thinking of capturing, sampling, applying some algorithms, resampling and letting the processed sound out
 
<it will be very difficult to do all that stuff in realtime at it demands a lot of processor time, which is not practical.
 
Bob, I think this is what you are looking for, but it does not sound too good.
___
[tt]
For N = 0 To wfh.data_Size - 1
'default = simple sine wave
B(N) = 127 + 127 * Sin(Frequency / wfh.nSamplesPerSec * 2 * Pi * N)
'if totaltime is > 1 sec
If Duration / 1000 > 1 Then
'if totaltime - currenttime < 1 sec
If Duration / 1000 - N / wfh.nSamplesPerSec < 1 Then
'apply damping using Exp function
B(N) = (B(N) - 127) * Exp(-(N / wfh.nSamplesPerSec - (Duration / 1000 - 1)) * 5) + 127
End If
End If
Next[/tt]
 
Well, it doesn't sound too bad, either. :) Neat piece of work, Hypetia. I never got how envelopes worked before.

Thanks,

Bob
 
Status
Not open for further replies.

Part and Inventory Search

Sponsor

Back
Top