How to play back a sound in Visual Basic twice - vb.net

I'm trying to build an educational app for a child with severe disabilities. It is supposed to teach the child how to add two numbers ranging from 1 - 9. In order to do so, I want to make it play back a sound, consisting of n (the number the child clicked) times the sound "one.wav". I use the following code:
For i As Integer = 1 To buttonNumber
i = i + 1
My.Computer.Audio.Play("one.wav")
Threading.Thread.Sleep(1000)
Next
While it does play the sound, the number of times is highly irregular and does not equal "buttonNumber". What is the problem? (Note that I've tried the same code without the "i = i + 1".)
EDIT:
The following piece of code does NOT work:
Private Sub PlayButtonAudio(buttonNumber As Integer)
Dim bytes As Byte() = IO.File.ReadAllBytes("one.wav")
For i As Integer = 1 To buttonNumber
My.Computer.Audio.Stop()
My.Computer.Audio.Play(bytes, AudioPlayMode.Background)
Threading.Thread.Sleep(playDuration)
Next
End Sub
Quite curiously though, it works perfecty whenever "buttonNumber" is larger than or equal to four.

The 1000 milliseconds you've specified is from the start of the playing wav, so you'll need the time gap to add the length of the sound. The audio begins to play through the OS and your code continues while it's playing. If you attempt to play it a second time while the first is still playing you'll run into problems.
There may be other methods of playing audio which don't return to your code until the audio is complete, but this would freeze your UI.
[Edit: Unless you do it in a separate thread. But for your purposes it's possibly simpler to just increase the delay.]

The My.Computer.Audio component is using a shared system resource and the Play method is just a wrapper around the API PlaySound function.
I am assuming that you only want the sound to play for a maximum of the sleep duration. If it takes the system longer than specified sleep duration (or a significant portion thereof) to load the WAV file into memory, the first playback may be mishandled. This effect can be mitigated by loading WAV into a byte array and using the Play overload that takes a byte array instead of a file name.
As you are using a shared resource, other sounds may be playing; calling My.Computer.Audio.Stop() will cancel anything currently playing.
Const playDuration As Integer = 1000 ' milliseconds
' load the WAV file into a byte array
' this will minimize the latency caused by loading the file from disc
Dim bytes As Byte() = IO.File.ReadAllBytes("one.wav")
For i As Integer = 1 To buttonNumber
My.Computer.Audio.Stop() ' cancel any currently playing sound
My.Computer.Audio.Play(bytes, AudioPlayMode.Background)
Threading.Thread.Sleep(playDuration)
Next
My.Computer.Audio.Stop() ' cancel playing if wav file duration is longer than playDuration

It turns out that I had the first three buttons twice in the handler list. Now it works perfectly.

Related

how does the process of reading a com port barcode work here?

I have a barcodescanner hooked up to a serialport. I read the Serialport via DataReceived-Event
Now there is some weird timing issue occurring on repetitive scans that I try to explain in the following. So can you explain to me what is causing this difference?
I use this barcode: 01513511220001.
I scan it twice with both examples
Example1:
Private Sub SerialPort1_DataReceived(sender As Object, e As IO.Ports.SerialDataReceivedEventArgs) Handles SP1.DataReceived
If SP1.BytesToRead > 0 Then
Threading.Thread.Sleep(1) '!!!
Dim str As String = SP1.ReadExisting
Debug.WriteLine(str)
End If
End Sub
'Output Scan 1:
'01513511220001
'Output Scan 2!!!:
'01513511220001
Example2:
Private Sub SerialPort1_DataReceived(sender As Object, e As IO.Ports.SerialDataReceivedEventArgs) Handles SP1.DataReceived
If SP1.BytesToRead > 0 Then
Dim str As String = SP1.ReadExisting
Debug.WriteLine(str)
End If
End Sub
'Output Scan 1:
'01513511220001
'Output Scan 2!!!:
'015135112
'20001
Note sometimes it cuts after the 9th digit sometimes it cuts after the 8th digit.
Understanding ReadExisting
You are using the ReadExisting function, which as per the documentation
Reads all immediately available bytes, based on the encoding, in both the stream and the input buffer of the SerialPort object.
As it says it immediately reads and gives you the data, even though it's not fully complete. A barcode scanner does not know how long the code is so it continuously reads it, it is up-to the software to make sense of it.
Understanding DataReceived
Similarly DataReceived method is called anytime there is data received via the port as per documentation (regardless of partial or full data, which depends on barcode scanner, internal buffers, etc.)
Indicates that data has been received through a port represented by the SerialPort object.
Again it is upto the software to make sense of the data being received.
Understanding your examples
The reason why your example 1 always works is because you add a delay before reading the actual data, giving a chance for the internal buffers to be full and hence you captured the full data upon calling ReadExisting
Note: If you have to add a delay to the code, it's always the wrong way to do it (extreme exceptions exist, but this isn't it)
Possible Solutions
I would suggest using ReadLine method but it has its challenges
Simply put a logic to wait till all 14 characters are read before moving to next step of processing (given you have a fixed size code, even not this can still be done but logic becomes more complex).
Crude Example for Solution 2
Private Sub SerialPort1_DataReceived(sender As Object, e As IO.Ports.SerialDataReceivedEventArgs) Handles SP1.DataReceived
If SP1.BytesToRead >= 14 Then 'Basically wait for the bytes to fill upto the length of the code
Dim str As String = SP1.ReadExisting
Debug.WriteLine(str)
End If
End Sub
Note: I have not ran the above example, it's just indicative to what needs to be done
Specifically for Honeywell 1911i
As per its manual, you need to ensure the Programming Interface for the device is set to RS232 Serial Port to do this refer to page 2-1 (page 27) of the manual under the heading Programing the Interface > RS232 Serial Port.
In this interface it automatically adds the suffix of Carriage Return and Line Feed, basically the NewLine character. Excerpt from the manual
The RS232 Interface bar code is used when connecting to the serial port of a PC or terminal. The following RS232 Interface bar code also programs a carriage return (CR) and line feed (LF) suffix, baud rate, and data format as indicated below. It also changes the trigger mode to manual.
This should allow you to use the ReadLine method to fetch the barcode without complexity.
You can set this interface by simply scanning the RS232 Interface Bar Code from the manual.

Visual Basic: Image moves up and and then back down after a button click

I am attempting to make a character appear to jump straight up in the air and then come back down and return to the same level he started at. (y=100) The code below seems to make the program fight itself and move him up and down at the same time.
I have tried countless methods and all of them resulted in the guy either going up and not coming back down or flying off the page.
Private Sub btnJump_Click(sender As Object, e As EventArgs) Handles btnJump.Click
tmrJump.Start()
End Sub
Private Sub tmrJump_Tick(sender As Object, e As EventArgs) Handles tmrJump.Tick
For intCounterUp As Integer = 100 To 15
picSpaceRunner.Location = New Point(intCounterX, intCounterY)
intCounterY = intCounterUp
Next intCounterUp
For intCounterDown As Integer = 15 To 100
picSpaceRunner.Location = New Point(intCounterX, intCounterY)
intCounterY = intCounterDown
Next intCounterDown
End Sub
End Class
The code is running with no delay, so you're at the mercy of the machine.
I'm not a professional game coder, so I couldn't explain the intricacies of modern game engines. However, one of the basic ideas I learned a long time ago is to control your game/animation loop. Consider the frames per second.
In your code, it could be as simple as adding a delay within each loop iteration. If you want the character to complete his jump in 2 seconds (1 second up, 1 second down), then divide 1000 (1 sec = 1000 ms) by the number of iterations in each loop and delay by that amount. For example, you have 85 iterations, so each iteration would take approximately 12 ms.
If you don't mind blocking a thread, you can do this very easily with Threading.Thread.Sleep(12). If blocking is an issue, you'll likely want to use an external timer.
I found this link during a Google search. He explains how to set up a managed game loop in VB.Net.
http://www.vbforums.com/showthread.php?737805-Vb-Net-Managed-Game-Loop
UPDATE: Per OP's comment...
To do this using timers, you'll want to manipulate the character object directly within the Timer event handler (Tick). You wouldn't use loops at all.
Set the Timer's Interval to the value discussed earlier - the number of ms corresponding to how long it takes to move 1 pixel. Then, in the Timer's Tick handler, set the character object's Location equal to a new Point with the new value. Also in the Tick handler, check your upper bound (15), then reverse the process until it hits the lower bound (100).
For example,
Private Sub tmrJump_Tick(sender As Object, e As EventArgs) Handles tmrJump.Tick
If (intCounterY > 15 And blnGoingUp == True) Then
picSpaceRunner.Location = new Point(intCounterX, intCounterY - 1);
End If
... Remaining Code Goes Here ...
End Sub
Do not put the loop in the timer_tick. Increase or decrease the height by set interval instead and then check if the image had reached the maximum or minimum height.

stdin is corrupt when reading it from non UI thread

I am trying to receive messages from Chrome using Google NativeMessaging, my WinForms application reads the messages sent from my extension in Chrome by calling the following function each 0.1s:
Public Function read_message() As String
'Read in first 4 bytes for length...
Dim stdin As Stream = Console.OpenStandardInput()
Dim bytelen(LENGTH_BYTE_COUNT - 1) As Byte
stdin.Read(bytelen, 0, LENGTH_BYTE_COUNT)
Dim msglen As Integer = BitConverter.ToInt32(bytelen, 0)
'Read the message
Dim msg As String = ""
For i As Integer = 0 To msglen - 1
msg &= ChrW(stdin.ReadByte())
Next i
stdin.Close()
stdin.Dispose()
Return msg
End Function
Everything works perfectly in the scenario I explained above, however, I wanted this function to run repeatedly in a different Thread.
So when I am creating new thread, that has similar timer with similar interval, the results become a mess, text messages from Chrome overlaps, some messages are simply missed, and some times return corrupt messages, and I can't understand why this works perfectly in UI thread, while gives strange results in a different one, even if I relax reading time interval, any clues?
P.S
As a side question, I am actually not sure that using a timer to read messages from stdin is best approach given that I have to use Winforms, is there any better way to detect when a message "arrives" into stdin stream?
Found the soltution here: Very Slow to pass “large” amount of data from Chrome Extension to Host (written in C#)
The problem was that stdin.ReadByte is very slow operation, and should be replaced with StreamReader(stdin).

Code Execution Sequence

I am a PLC programmer who is currently using a variant of VB to control a motor.
I want to call a function that will execute moves and not return to the main code until the move has been completed. Currently here is what I have:
Program 'Main Program
While 1
If move_req = 1
Function MoveMotor
End If
Wend
End Program
Function MoveMotor
MoveABS 10 ' Move to encoder position 10mm
move_complete = 1
While move_req = 1
'Do Nothing
Wend
End Function
For some reason this code isn't working and the move command is being sent over and over again. Could this be because the main program continues to run when the function is running? Is that how VB works? I am used to thinking of code sequence in terms of PLC's where they scan through everything repeatedly at a certain frequency.
Whenever the move is complete, there must be a way for it to be detected by the program. It looks like you want move_req to be set to zero when this happens, but I cannot see what would cause that. How does the machine signal the program that it's finished moving?
A second point is that when you have a loop that waits while it checks for variable change, it can cause a CPU spike. You can put a pause in the loop with some thing like System.Threading.Thread.Sleep(100) where 100 is milliseconds to pause.

Out of memory when opening images using Stream

I hope you can help with what is probably a naive use of Streams in VB.net.
I have a program that batch processes images and am running out of memory making repeated use (in a loop) of the following:
Using str As Stream = File.OpenRead(file_stem + CStr(file_number) + "." + file_extension)
temp_img = Image.FromStream(str)
str.Close()
End Using
PictureBox1.Image = temp_img
bm = PictureBox1.Image.Clone
temp_img is globally declared Dim temp_img As Image. bm is declared in the same Sub routine as the loop Dim bm As Bitmap.
As the program runs I can see in Task Manager the memory usage rising and then it crashes with an out of memory error. It's as if each time I am using the Stream it is keeping the memory used. What am I doing wrong here?
EDIT:
This thread appears to have gone cold now, but I thought I would share my "work around" for this. It would seem that this is a VB.net bug as the way I have fixed it is to add a MsgBox which is shown only one, immediately prior to the first call to the subroutine that processes sets of 60 images. I ran a single job that processed 96 sets of 60 images and the memory usage didn't rise above about 45MB. The important thing that makes me think it is a bug is that I only show the MsgBox before the first set of 60 and all are run in series. Showing an MsgBox shouldn't fix anything in itself anyway!