![]() |
|
![]() |
|
|
Thread Tools | Display Modes |
|
|
#1 |
|
Newbie
Join Date: Nov 2004
Posts: 18
Rep Power: 0
![]() |
I wanted to make a simple app that draws that waveform of a wavefile. I actually made a lot of progress in one day and got it to work. But then I thought about restructuring my code a little bit and ran into all manner of problems.
The biggest problem I'm having now is understanding how my app draws a meaningful wave even when the array from which it draws its values has each of its elements set to 0. There is no point in the entire app when I am filling this array with values, and yet when I launch the program I'm staggered to find a well-formed wave. Its a bit of an elaborate problem so please bear with me as I try to explain... I'm going to dump my code here because I have no idea where the problem may lie /*
Questions go here:
1. is a reference another word for handle?
2. research TCHAR datatype.
3. Difference between WNDCLASSEX and WNDCLASS?
WNDCLASSEX is the newer one. There are more differences but for our purposes
lets assume that the newer one is the "better one".
*/
//always necessary to include windows.h for making windowed programs
#include <windows.h>
#include "stdafx.h"
#include <SP.h>;
//function prototypes
short int* extractChannelValues(short int* PCM_DATA, long arraySize, int speaker);
short int* calculatePoints(short int* speakerData, long arraySize);
void drawAxis();
void draw(short int* graphPoints, int Resolution, int speaker);
short int modulus(short int value);
ATOM produceAndregisterClass();
int createAndShowWindow();
LRESULT CALLBACK WindowProcedure
(HWND hwnd, unsigned int message, WPARAM wParam, LPARAM lParam);
//global variables follow
HINSTANCE hinst; //a reference to the application instance
HWND mainWindow; //this reference will eventually point to the main Window,
//once instantiated
TCHAR className[] = "myWindow"; //the name of the "class" which we will register
short int* PCM_Data;
long sampleValues = 0;
short int* LEFT_SPEAKER;
short int* LEFT_SPEAKER_GRAPH_POINTS;
/* Main Steps
1. Define a window "class". Populate a WND Class Ex structure.
2. Register the window class. Pass the WNDCLASSEX struct pointer to RegisterClassEx.
3. Provide a windows procedure. This is the function which will deal with the user's
interaction with the window.
4. Create and show the window.
5. Put application into a "message loop".
*/
//entry point into a windowed application is main
int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
{
/*load wave file into memory*/
WAVEDATA drawData = validWAVEFile("x3.wav");
PCM_Data = drawData.PCM_Data;
sampleValues = drawData.dataSize;
/*extract the information for left channel*/
LEFT_SPEAKER = extractChannelValues(PCM_Data, sampleValues, 0);
LEFT_SPEAKER_GRAPH_POINTS = calculatePoints(LEFT_SPEAKER, sampleValues/2);
hinst = hInstance;
if ( produceAndregisterClass() == 0 )
return -1; //producing and registering class did not work for whatever reason
//thus we will exit early.
if ( createAndShowWindow () == 0 )
return -1; //again, something went wrong so exit early.
ShowWindow(mainWindow,nCmdShow);
UpdateWindow(mainWindow);
//message loop goes here
BOOL bRet;
MSG msg;
while( (bRet = GetMessage( &msg, NULL, 0, 0 )) != 0)
{
if (bRet == -1)
{
return -1;
}
else
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
}
return 0;
}
ATOM produceAndregisterClass()
{
//first let us populate a WNDCLASSEX struct.
//this struct contains all the details of windows we will display
WNDCLASSEX windowClass = { 0 };
//set the size. when dealing with WNDCLASSEX structures, the RHS of the
//code is always the same when setting the cbSize member
windowClass.cbSize = sizeof(WNDCLASSEX);
//a style helps determine the window's behavior. these are enumerated
//at the MSDN website.
//it is my understanding that using the | operator allows you to specify a combination of styles
//I've set it to redraw the window whenever the height or width is adjusted.
windowClass.style = CS_HREDRAW | CS_VREDRAW;
//pointer to the programmer defined window procedure to deal with the user interaction with window
windowClass.lpfnWndProc = (WNDPROC) WindowProcedure;
//number of extra bytes to allocate to structure. i'm saying 0 because this is a simple window,
//although it remains to be seen exactly how you work how much more you need if the window was
//"complex".
windowClass.cbClsExtra = 0;
windowClass.cbWndExtra = 0;
//assign the reference to the application instance
windowClass.hInstance = hinst;
//assignment of an icon. NULL means system provides default.
windowClass.hIcon = 0;
//assignment of cursor. NULL means default.
windowClass.hCursor = 0;
//background colour assignment, this can be a handle to a data type called HBRUSH or
//alternatively it can be a specified colour (if the latter must cast to HBRUSH).
//always must add 1 to chosen colour.
windowClass.hbrBackground = (HBRUSH) (COLOR_WINDOW + 1);
//this should be a string pointing to a resource that specifies a menu.
//but a NULL value means no menu, which is what we want for this simple window.
windowClass.lpszMenuName = 0;
//assign the class name (which we've stored in a global variable)
windowClass.lpszClassName = className;
//i guess this icon member is for the icon your app should have when it is listed
//in a directory. don't really know but its not that important. we'll just use NULL.
windowClass.hIconSm = 0;
//The structure is filled and now we must register it.
return RegisterClassEx(&windowClass);
}
//call this function to create and display window only after registering the class.
int createAndShowWindow()
{
mainWindow = CreateWindow( className,
0,
WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT,
0,
CW_USEDEFAULT,
0,
0,
0,
hinst,
0);
/*
mainWindow = CreateWindowEx(
WS_EX_APPWINDOW,//window behavior
className,
"Test Window", //title bar string
WS_CAPTION, //style
CW_USEDEFAULT, //x position
CW_USEDEFAULT, //y position
CW_USEDEFAULT, //width
CW_USEDEFAULT, //height
0, //handle to parent window, but there is no parent so...?
0, //menu handle. null means use the one specified in class
hinst, //handle instance
0 //lparam value...not really sure what its for
);
*/
if ( !mainWindow ) return 0;
return 1;
}
//this function always has this signature and it deals with the user interaction
//messages are passed from the operating system to this function which processes
//the messages
LRESULT CALLBACK WindowProcedure
(HWND hwnd, unsigned int message, WPARAM wParam, LPARAM lParam)
{
//the window will do nothing, except vanish when we ask it to
//if there is a message we have not dealt with in the case statement
//we use the defaultwindowprocedure called DefWindowPoc.
switch (message)
{
// PAINTSTRUCT ps;
//HDC hdc;
case WM_PAINT:
drawAxis();
/*get the co-ordinates for the left channel graph sub graph*/
draw(LEFT_SPEAKER_GRAPH_POINTS, 1024, 0);
return 0;
/* hdc = BeginPaint(hwnd, &ps);
TextOut(hdc, 0, 0, "Hello, Windows!", 15);
EndPaint(hwnd, &ps);
return 0L;*?
*/
case WM_DESTROY:
delete (LEFT_SPEAKER_GRAPH_POINTS);
PostQuitMessage (0);
return 0;
}
return DefWindowProc (hwnd, message, wParam, lParam );
}
void draw(short int* graphPoints, int Resolution, int speaker)
{
int silentVoltage = 0;
if (speaker == 0) silentVoltage = 180; else silentVoltage=540;
int x = 0;
HDC hdc;
hdc = GetDC(mainWindow);
MoveToEx(hdc, x, silentVoltage, (LPPOINT) NULL);
int sample = 0;
while ( x < Resolution )
{
sample = graphPoints[x];
sample=sample/182;
if (sample > 0) sample = silentVoltage - sample;
if (sample < 0) sample = modulus(sample) + silentVoltage;
if (sample == 0) sample = silentVoltage;
LineTo(hdc, x, sample);
x++;
}
ReleaseDC(mainWindow, hdc);
}
void drawAxis()
{
HDC hdc;
hdc = GetDC(mainWindow);
/* POLYbezier function for one curve needs
1 point for starting point
2 point for control point
3 point for control point
4 point for where we are going to
POINT points[] = { 100,508, 125, 75, 150, 125, 175, 100};
//MoveToEx(hdc, 500, 500, (LPPOINT) NULL);
PolyBezier(hdc, points, 4);
*/
//draw line dividing left speaker and right speaker
int x = 0;
int y = 360;
MoveToEx(hdc, (int) x, (int) y, (LPPOINT) NULL);
LineTo(hdc, (int) x + 1023, (int) y);
//draw 0 volt line for left speaker
x = 0;
y = 180;
MoveToEx(hdc, x, y, (LPPOINT) NULL);
LineTo(hdc, (int) x + 1023, (int) y);
//draw 0 volt line for right speaker
x = 0;
y = 540;
MoveToEx(hdc, x, y, (LPPOINT) NULL);
LineTo(hdc, (int) x + 1023, (int) y);
//move the pen back to the start of the left speaker
x=0;
y=180;
MoveToEx(hdc, x, y, (LPPOINT) NULL);
ReleaseDC(mainWindow, hdc);
}
short int* calculatePoints(short int* speakerData, long arraySize)
{
//how many values to plot along x-axis
int horizontalResolution = 1024;
//declare return value array
short int* GraphPoints;
GraphPoints = new short int[horizontalResolution];
//calculate how many samples each pixel is representative of
int bufferSize = arraySize / horizontalResolution;
if ( bufferSize % 2 ) bufferSize++;
short int* buffer;
buffer = new short int[bufferSize];
short int sample = 0; //the value to plot at the i'th iteration
long i = 0; //the loop counter
int x = 0; //the pixel for the current iteration
return GraphPoints;
}
short int* extractChannelValues(short int* PCM_DATA, long arraySize, int speaker)
{
long i = 0;
long speakerValues = arraySize / 2;
short int* channelValues;
channelValues = new short int[speakerValues];
if ( speaker == 0 ) i=0; else i=1;
long x=0;
while ( x < speakerValues )
{
channelValues[x]=PCM_Data[i];
i= i+2;
x++;
}
return channelValues;
}
short int modulus(short int value)
{
if (value < 0) return value*-1;
else return value;
}don't worry, i'm not asking you to read all of it - i'll fill you in on them most salient details. first i read in a wave file called x.wav. By read in I mean I store all the amplitude information in the wavefile into a short int array. In the code this array is the global variable PCM_Data. next step: i declare another short int array called LEFT_SPEAKER. Every odd value in the PCM_Data array corresponds to sounds coming out of the left speaker, so I copy all of them into the array called LEFT_SPEAKER. Now here's the puzzling bit. I have yet another array called LEFT_SPEAKER_GRAPH_POINTS which is to be populated with data that I will use to draw the waveform for the leftspeaker. This is exactly 1024 elements across because I am assuming a horizontal resolution of 1024. I use the function called calculatePoints() to populate LEFT_SPEAKER_GRAPH_POINTS with the necessary values. How I did this previously was to do some basic processing on the values of the LEFT_SPEAKER array. However since my new code was messing up on me I cleaned out the calculatePoints() function so that it is effectively returning an empty array. And yet when I launch the program, I am greeted with this waveform: ![]() i just don't understand how on earth it can draw such a good-looking waveform when i haven't even supplied it with the data?? And if you're curious, no it looks nothing like the "true" waveform for the file x.wav. If anyone could shed any light on this topic, please please let me know. I have spent 2 days wondering why on earth my program is behaving so strangely. it almost seems to defy logic! |
|
|
|
|
|
#2 |
|
Expert Programmer
|
Oooooooooooooooookkkkkkk... this is going to be a bit of speculation, but should give you an idea as to where to start assessing this problem.
One of the most interesting aspects about memory allocation in C/C++ is that memory will not intialize when it is allocated until you initialize it manually. So what happens is that if you allocate memory on the heap or the stack unless you specify an initial vlue, or any value trying to read from the variable will likely succeed, and the value be true... just somewhat "random" Random as in it will read back the previous contents of that memory location. When memory is "deleted" it is not actually cleared, the memory location is just marked as available, which as you can see creates this problem. My guess is that somewhere in there you are not initialize an variable/array, or are setting a pointer to point to an uninitialized chunk of memory, maybe even unallocated (usually this throws an Access Violation however). An interesting thing to note in the image you provided is that while the waveform seems a bit repetitive, it is not perfectly such, which is also very plausible given the circumstances I explained above. The best way to fix this error is to likely set a breakpoint and check the datablock which represents the waveform, or where the waveform should be, figure out what is writing to this datablock, if the datablock is ever even being initialize to 0, or NULL, and if something could be writing to the datablock when it shouldn't be. Hopefully this helps some...
__________________
Clifford Matthew Roche <geek@cliffordroche.com> Web Hosting: http://www.crd-hosting.com Consulting: http://www.crdev-consulting.com |
|
|
|
|
|
#3 |
|
Newbie
Join Date: Nov 2004
Posts: 18
Rep Power: 0
![]() |
thanks, yeah I think you are right. It must have something to do with old data being thrown back onto the screen.
So if I manually make sure I muck around with the contents of the array, I shouldn't get this waveform appearing. well thats the theory anyway. I'll let you know how I get on. thanks for the info though - this language has a thousand and one quirks I have to get used to. I've been raised on Java and that kind of holds your hand and never lets anything go wrong. C++ seems a lot more down and dirty and DIY. |
|
|
|
|
|
#4 |
|
Newbie
Join Date: Nov 2004
Posts: 18
Rep Power: 0
![]() |
OK here's the thing....
in my message loop, if the WM_PAINT message is recieved I call the draw function. The draw function refers to a global array called LEFT_SPEAKER_GRAPH_POINTS (which has already been populated with data by calculatePoints() ). The draw function uses the lineto function to draw the data. However no matter what it always draws a nothing to the screen, almost as if the array is empty (even though I have now processed it in such a way as to make sure the array has got data in it). BUT! If in the draw function, I load up the wavefile in there and use that resulting array to draw to the screen, it works! But I don't want it to work this way because it means having to constantly load the file back into memory per every reception of the WM_PAINT message. The whole idea of restructuring my code was to analyse the wavefile first, get all the graph points out, save those to a global array and plot that whenever the draw function is called. But like I said, that approach results in an empty screen :huh: Is there a specific way of sending arrays between functions - it almost as if their content is spilling out in transit! So here's the new way, which doesn't work: /*global variables*/
short int* PCM_Data; //data from the wavefile, for both speakers
long sampleValues = 0; //number of entries to PCM_Data
short int* LEFT_SPEAKER; //all the values from PCM_Data that are meant for the left speaker
short int* LEFT_SPEAKER_GRAPH_POINTS; //the y-co ordinates for the graph
...
//in the main function
/*load wave file into memory*/
WAVEDATA drawData = validWAVEFile("1000.wav");
PCM_Data = drawData.PCM_Data;
sampleValues = drawData.dataSize;
/*extract the information for left channel*/
LEFT_SPEAKER = extractChannelValues(PCM_Data, sampleValues, 0);
LEFT_SPEAKER_GRAPH_POINTS = calculatePoints(LEFT_SPEAKER, sampleValues/2);
.... //some code to build and insantiate the window
//in the window procedure function
switch (message)
{
case WM_PAINT:
drawAxis();
/*get the co-ordinates for the left channel graph sub graph*/
draw(LEFT_SPEAKER_GRAPH_POINTS, 1024, 0);
return 0;
....
}
//now the code for the calculatePoints function
short int* calculatePoints(short int* speakerData, long arraySize)
{
//how many values to plot along x-axis
int horizontalResolution = 1024;
//declare return value array
short int* GraphPoints;
GraphPoints = new short int[horizontalResolution];
//calculate how many samples each pixel is representative of
int bufferSize = arraySize / horizontalResolution;
if ( bufferSize % 2 ) bufferSize++;
short int* buffer;
buffer = new short int[bufferSize];
short int sample = 0; //the value to plot at the i'th iteration
long i = 0; //current index of the sample array
int x = 0; //the pixel for the current iteration
double RMS = 0; //variable for ROOT MEANS SQUARE calculation
while ( true )
{
for (int j=i; j<bufferSize; j++)
{
/*square and sum the values*/
RMS+= (speakerData[j+i])*(speakerData[j+i]);
}
RMS = RMS/bufferSize;
RMS = sqrt(RMS);
GraphPoints[x]=(short int) RMS;
i=2;
x++;
if (x==1024) break;
}
}
and finally the draw function
void draw(short int* graphPoints, int Resolution, int speaker)
{
int silentVoltage = 0;
if (speaker == 0) silentVoltage = 180; else silentVoltage=540;
int x = 0;
HDC hdc;
hdc = GetDC(mainWindow);
MoveToEx(hdc, x, silentVoltage, (LPPOINT) NULL);
long i=0;
short int sample = 0;
while ( x < Resolution )
{
sample = graphPoints[x];
sample=sample/180;
if (sample > 0) sample = silentVoltage - sample;
if (sample < 0) sample = modulus(sample) + silentVoltage;
if (sample == 0) sample = silentVoltage;
LineTo(hdc, x, (int) sample);
x++;
i=i+100;
}
ReleaseDC(mainWindow, hdc);
}now contrast all that to if i just load up the wavefile from within the draw function and start to randomnly draw values from it onto the screen (THIS method works, but why doesn't the other?) void draw(short int* graphPoints, int Resolution, int speaker)
{
WAVEDATA wave = validWAVEFile("x3.wav"); //load up wave in draw function
int silentVoltage = 0;
if (speaker == 0) silentVoltage = 180; else silentVoltage=540;
int x = 0;
HDC hdc;
hdc = GetDC(mainWindow);
MoveToEx(hdc, x, silentVoltage, (LPPOINT) NULL);
long i=0;
short int sample = 0;
while ( x < Resolution )
{
sample=wave.PCM_Data[i];
sample=sample/180;
if (sample > 0) sample = silentVoltage - sample;
if (sample < 0) sample = modulus(sample) + silentVoltage;
if (sample == 0) sample = silentVoltage;
LineTo(hdc, x, (int) sample);
x++;
i=i+2;
}
ReleaseDC(mainWindow, hdc);
} |
|
|
|
![]() |
| Bookmarks |
| Currently Active Users Viewing This Thread: 1 (0 members and 1 guests) | |
| Thread Tools | |
| Display Modes | |
|
|