I've been use this code to read .wav data in delphi and i've been comparing the result with value i got from matlab function wavread. From which i can say that matlab function can automatically recognize which one is the sample data value, but not with delphi (but both matlab and delphi code result is the same). Since my delphi code cannot recognize the sample data value, i look through the array and found out that the index where sample data value starting were different each .wav file. For an example i test some .wav file and get this:
classic1.wav the sample data value starting on wavedata[].Data[] index number 40
classic2.wav the sample data value starting on wavedata[].Data[] index number 35
i got above conclusion by looking at the result y,[y, Fs, nbits, opts]=wavread('classic1.wav','double'); then i go to the result delphi return in an array checking it value and find the exact same value starting in the index 40 for classic1.wav and 35 for classic2.wav. And i want to know if there is a way i can know the starting index of sample data value of each .wav file?
EDIT : i have corrected the record similar to the reference given, it's perfectly right with the header(from ChunkID to Subchunk2size) but i still got confused by the sample data following it because no change from the previous result.
type
TWaveHeader = packed record
Marker_RIFF: array [0..3] of char;
ChunkSize: cardinal;
Marker_WAVE: array [0..3] of char;
Marker_fmt: array [0..3] of char;
SubChunkSize: cardinal;
FormatTag: word;
NumChannels: word;
SampleRate: longint;
ByteRate: longint;
BlockAlign:word;
BitsPerSample: word;
Marker_data: array [0..3] of char;
DataBytes: longint;
end;
TChannel = record
Data : array of smallint;
end;
You're obviously not skipping all of the header fields correctly. Wav files can have some optional header information, so though the actual sample values normally start at byte 44, this is not always the case.
See here for example: https://ccrma.stanford.edu/courses/422/projects/WaveFormat/
One way to skip directly to the sample data (after reading whatever parts of the header you require) is the scan the file (four bytes at a time) for the ascii string of "data" (64 61 74 61 hex) and then read the 4 bytes that immediately follow that, which (as cardinal or longword) represents the total number of bytes to read. The actual samples follow immediately after that cardinal.
Edit:
As expected, looking at the files Classic1.wav and Classic2.wav in a hex editor it's clear that they both have some metadata. At location 36 in each file, instead of finding the SubchunkID of "data" you instead find "LIST". The four bytes following this give the size of this additional data. This is what you have to skip over in order to get to the music sample data.
For example, Classic1.wav has 148 bytes of extra data starting at offset 44. This places Subchunk2ID at offset 192 and Subchunk2Size at offset 196, meaning that the first sample starts at offset 200 in the file.
Classic2.wav has 128 bytes of extra data starting at offset 44. This places Subchunk2ID at offset 172 and Subchunk2Size at offset 176, meaning that the first sample starts at offset 180 in the file.
Here is Classic2.wav in a very basic hex editor:
Rather than doing all of the file I/O manually, you should use the Win32 Multimedia API functions instead - mmioOpen(), mmioDescend(), mmioAscend(), mmioRead(), etc. Let them do all of the hard work for you. Your code will be easier to manage and read, as you will be able to focus more on the content of the individual chunks while letting the API handle the low-level details of finding each chunk for you.
Related
I am trying to organize saving and loading of data changing in size. So the save file needs to store several (unknown and every time different number) of dynamic arrays.
The mistake appears in this MCVE:
procedure TAnnMainF.Button6Click(Sender: TObject);
var
f: file;
ari, aro: array of double;
i, Count: word;
begin
SetLength(aro, random(5) + 1);
for i := 0 to High(aro) do
aro[i] := random(2001) / 2000 - 1;
AssignFile(f, 'c:\delme\1.txt');
ReWrite(f);
Count := Length(aro);
BlockWrite(f, Count, SizeOf(word));
BlockWrite(f, aro[0], SizeOf(double) * Count);
CloseFile(f);
Reset(f);
BlockRead(f, Count, SizeOf(word));
BlockRead(f, ari[0], SizeOf(double) * Count);
CloseFile(f);
end;
This code results in I/O error 998.
I was trying to declare the type TDoubleArray = array of Double; and pass ari as a parameter in BlockRead. I also tried to SetLength(ari, Count) before I call BlockRead without any success.
The Answer to this question did not help me.
The code reads the Count properly but rises an exception at array loading.
What am I doing wrong?
You must set the size of the block in the ReWrite/Reset commands:
ReWrite(f,1);
...
Reset(f,1);
From documentation:
RecSize is an optional expression that can be specified only if F is an untyped file. If F is an untyped file, RecSize specifies the record size to be used in data transfers. If RecSize is omitted, a default record size of 128 bytes is assumed.
This means that reading the data will overflow the allocated buffer, hence the I/O error from the system.
Also read this warning about using ancient file I/O BlockRead/BlockWrite:
Warning: This is an older method that is particularly dangerous to use because of the untyped Buf parameter, leading to potential memory corruption. The record size used by BlockRead and BlockWrite is governed by the optional 2nd parameter to the Reset or Rewrite call that was used to open the file being written. It is preferable to use streams in your applications. For example, a user procedure involving a stream can use both TMemoryStreams and TFileStreams, instead of being limited to using files as with these older routines.
In general the speed difference between BlockRead/Write and streams is insignificant. For larger files, a buffered handler is preferred.
There is an excellent example of a buffered file stream handler from David: Buffered files (for faster disk access)
As #kami/#TomBrunberg noted and what you tried, you must also allocate the length of the ari dynamic array before reading the data.
Continuing from the topic here, being unfamiliar with pointers,
how to correctly call the function bellow, given the fact that the pointers in it are the data to be retrieved back, and how to actually get to that data after calling it?
function RetrieveDSOData(whatchannels: Byte; DSOCH1, DSOCH2: PDouble; LADATA: PWord; Nth_Sample: Byte): longint; cdecl; external 'E_l80.dll';
(Needed DSOCH1, DSOCH2 and LADATA data...)
If it's in any help / important at all -> the documentation states that these are "pointers to an array".
In case this is a duplicate and I just didn't look by correct keywords, vote for closing this.
Thank you.
Edit:
It is to be assumed the size can be received by the return of the function. Documentation states:
Return: The number of samples in the DSO and LA arrays. This number may not
be (total blocks x 1024) as some samples at the beginning and end are thrown away for
various purposes.
So, max size might be (1024 x 32samples x 2 (2x8 digital channels, 2 bytes?)) for LADATA array...
You have to call the function with addresses of arrays, and it will fill these arrays. Example:
var
DS1: array[0..31] of Double;
RetrieveDSOData(whatchannels, #DS[0], ...
Why i get NAN value when trying to read .wav file and directly store it sample data in double? Before thinking about using this i was store the sample data in smallint and then convert it to double by dividing it with 32768.0 (there is no NAN value) but later on i got accuracy problem with rounding off when converting it back to wav file.
SetLength(buf, ckiData.cksize);
mmioRead(HMMIO, PAnsiChar(buf), ckiData.cksize);
Where buf are array of double. Weren't it allowed to directly storing it into array of double?
If the raw sample data really is 64-bit doubles (what audio format are you using that does that?), then yes, you can directly read into an array of doubles, eg:
var
buf: array of Double;
SetLength(buf, ckiData.cksize div SizeOf(Double));
mmioRead(HMMIO, PAnsiChar(buf), Length(buf) * SizeOf(Double));
However, most audio formats do not use doubles, so you have to first read into a suitable buffer using the correct data type (Smallint for 16-bit PCM, for example), then convert the samples afterwards.
in my project I need to developp server receiving frames from GPRS/GPS Box and decode theses frames to extract relevant data as latitude , longitude and more
the first part (TCP connection and receiving data) is done , the problem I had is with decode data, the firmware of GPRS box send data not in string format but in hex format , so the methode I used (currentReaderBuffer) ouput the frame with string format , let me explain with real example :
The data sent from GPRS BOX is : 0d 0a 1f 52
data received using currentReaderBuffer is : #$d#$a#1fR
the problem is how can I know if the caracter #$d correspond to 0d or each of the caracters (#,$,d) correspond to each ascii code
#$d means it's a Char (#) in hex ($) with the value D (13), which means it's a carriage return. If you're always getting 1 byte values (for instance 0D or `1F'), you can be pretty sure they're hex values and convert them.
Converting them is easy. Just use them.
For instance:
ShowMessage('You received hex 52, which is ' + #$52);
You're, in fact, receiving the correct data, but you're interpreting it in a bad way... and mixing some concepts:
Hex is just a representation of data... data in a computer is binary and computers does not understand nor work in hex.
You are choosing to represent a byte of data as a character, but you can treat it as a byte and from that perform a hex representation of that data.
For example:
var
aByte: Byte;
begin
//ReceivedStr is the string variable where you hold the received data right now
aByte := ReceivedStr[1];
//Warning! this will work only in pre-2009 Delphi versions
ShowMessage('Hexadecimal of first byte: ' + IntToHex(aByte);
end;
or
function StringToHex(const S: string): string; //weird!
begin
//Warning! this will work only in pre-2009 delphi versions
Result := '';
for I := 1 to Length(Result) do
Result := Result + IntToHex(Byte(S[I])) + ' ';
end;
function ReceiveData();
begin
//whatever you do to get the data...
ShowMessage('Received data in hex: ' + StringToHex(ReceivedStr));
end;
This said, IMHO is better to treat the data as binary from the start (Integers, bytes or any other suitable type), avoiding using strings. It will make your life easier now and then, when you want to upgrade to modern Delphi versions, where strings are Unicode.
Anyway, you may want to process that data, I don't think your intention is to show it directly to the user.
If you want to check if a particular byte against a hex value, you can use the $ notation:
if aByte = $0d then
ShowMessage('The hex value of the byte is 0d');
I need to read a large (2000x2000) matrix of binary data from a file into a dynamic array with Delphi 2010. I don't know the dimensions until run-time.
I've never read raw data like this, and don't know IEEE so I'm posting this to see if I'm on track.
I plan to use a TFileStream to read one row at a time.
I need to be able to read as many of these formats as possible:
16-bit two's complement binary integer
32-bit two's complement binary integer
64-bit two's complement binary integer
IEEE single precision floating-point
For 32-bit two's complement, I'm thinking something like the code below. Changing to Int64 and Int16 should be straight forward. How can I read the IEEE?
Am I on the right track? Any suggestions on this code, or how to elegantly extend it for all 4 data types above? Since my post-processing will be the same after reading this data, I guess I'll have to copy the matrix into a common format when done.
I have no problem just having four procedures (one for each data type) like the one below, but perhaps there's an elegant way to use RTTI or buffers and then move()'s so that the same code works for all 4 datatypes?
Thanks!
type
TRowData = array of Int32;
procedure ReadMatrix;
var
Matrix: array of TRowData;
NumberOfRows: Cardinal;
NumberOfCols: Cardinal;
CurRow: Integer;
begin
NumberOfRows := 20; // not known until run time
NumberOfCols := 100; // not known until run time
SetLength(Matrix, NumberOfRows);
for CurRow := 0 to NumberOfRows do
begin
SetLength(Matrix[CurRow], NumberOfCols);
FileStream.ReadBuffer(Matrix[CurRow], NumberOfCols * SizeOf(Int32)) );
end;
end;
No, AFAIK there's no way to use RTTI to set up multidimensional arrays. But if you're using Delphi 2010, you should be able to use generics, like so:
type
TRowData<T> = array of T;
procedure ReadMatrix<T>;
var
Matrix: array of TRowData<T>;
NumberOfRows: Cardinal;
NumberOfCols: Cardinal;
CurRow: Integer;
begin
NumberOfRows := 20; // not known until run time
NumberOfCols := 100; // not known until run time
SetLength(Matrix, NumberOfRows, NumberOfCols);
for CurRow := 0 to NumberOfRows do
FileStream.ReadBuffer(Matrix[CurRow][0], NumberOfCols * SizeOf(T)) );
end;
This will have to be in a class, though, as Delphi 2010 doesn't support standalone procedures with generic types. Once you've got this set up, you can call TWhateverClass.ReadMatrix<integer>, TWhateverClass.ReadMatrix<int64>, TWhateverClass.ReadMatrix<single>, and so on.
Also, if you have a multidimensional array with X dimensions, you can pass X length parameters to SetLength, not just one. So use one call to SetLength(Matrix, NumberOfRows, NumberOfCols) outside the loop, instead of initializing each row separately to the same width.