/////////////////////////////////////////////////////////////////////////////// // File: waveedit.cxx // Written by: Michael Main // // This program is a simple wave editor with an awkward user interface. // Its purpose is to show some of the wave functions from: // http://msdn.microsoft.com/library/ // default.asp?url=/library/en-us/multimed/htm/ // _win32_about_waveform_audio.asp // // When compiling: bgi++ -Wall -gstabs waveedit.cxx -lwinmm -o waveedit // // Planned commands: // 'p' or 'P': Play the current wave from start to end [Implemented] // 's' or 'S': Play the selected part of the current wave // KEY_UP: Current wave changes to one above where it is now [Implemented] // KEY_DOWN: Current wave changes to one below where it is now [Implemented] /////////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////// // Include directives #include // fill_n #include // M_PI, sin #include // strlen, strcpy #include // cin, cout #include // all the winbgi functions #include // all the winmm functions using namespace std; /////////////////////////////////////////////////////////////////////////////// const double M_PI = 3.14159265; /////////////////////////////////////////////////////////////////////////////// // Constants const int MANY_WAVES = 4; // Number of waves to display const int WAVE_HEIGHT = 100; // Height of the display area for each wavedaa const int WAVE_WIDTH = 340; // Width of the display area for each wave const int VERTICAL_BORDER = 30; // Border (in pixels) above each wave display const int SIDE_BORDER = 50; // Border (in pixels) beside each wave display const int WINDOW_WIDTH = // Total width of the graphics window WAVE_WIDTH + 2*SIDE_BORDER; const int WINDOW_HEIGHT = // Total height of the graphics window (WAVE_HEIGHT+VERTICAL_BORDER)*MANY_WAVES + VERTICAL_BORDER; /////////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////// // A sound structure holds all the information about a single sound that we // are editing. Each sound has information about its wave data. // A portion of this wave data can be displayed on the screen. // Another portion may be selected for cutting, copying, and other editing. // A single sample can be highlighted in a special color. // The top, left, width and height are pixel coordinates for where the // sound should be drawn. If this is the current sound (for editing), // then current is true and the display has a green border around it. struct sound { WAVEHDR header; // Mostly the data buffer WAVEFORMATEX format; // Information such as samples/second long display_start; // Which sample number at left of display long display_end; // Which sample number at right of display long select_start; // Starting sample number for selected part long select_end; // Ending sample number for selected part long cursor; // Sample number where the cursor lies int top, left, width, height; // Where to display this wave bool current; // Is this the current wave for editing? }; /////////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////// // Prototypes void copy_to_cb(sound& s, long start, long end); int current(sound data[]); void draw(const sound& s); void draw_line(int x, int top, int center, int bottom, int dy, int b, int f); void execute(sound data[], char command); void initialize(sound& s, int index); char next_request( ); void play(sound& s); /////////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////// int main( ) { int i; sound data[MANY_WAVES]; char command; // Play an introductory sound, initialize all sounds, and open the window: initwindow(WINDOW_WIDTH, WINDOW_HEIGHT, "Wave Editor"); MessageBeep(MB_ICONASTERISK); delay(1000); MessageBeep(MB_ICONEXCLAMATION); delay(1000); MessageBeep(MB_ICONHAND); delay(1000); MessageBeep(MB_ICONQUESTION); delay(1000); MessageBeep(MB_OK); delay(1000); sndPlaySound("woof.wav", SND_SYNC); for (i = 0; i < MANY_WAVES; ++i) { initialize(data[i], i); draw(data[i]); } do { command = next_request( ); execute(data, command); } while (command != 'q'); } /////////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////// void copy_to_cb(sound& s, long start, long end) // This function copies part of a sound to the clipboard in the CB_WAVE // format. The copied portion is from sample number start to sample // number end. { HGLOBAL global; // Example to show how to copy a string to the clipboard char message[] = "Hello, World!"; global = GlobalAlloc(GMEM_FIXED, strlen(message)+1); strcpy((char *)global, message); OpenClipboard(NULL); EmptyClipboard( ); SetClipboardData(CF_TEXT, global); CloseClipboard( ); } /////////////////////////////////////////////////////////////////////////////// int current(sound data[]) { int i; for (i = 0; i < MANY_WAVES; ++i) if (data[i].current) return i; return -1; } /////////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////// void draw(const sound& s) // This function draws a representation of the sound on the screen. // The implementation has some flaws: Mainly, I think the information about // the actual sound should be separated from the information about its // display, but never mind for now. { int i; long previous_index, index; bool highlighted; bool selected; int x; int dy; const int DISPLAY_WIDTH = s.display_end - s.display_start + 1; const int TOP = s.top; const int CENTER = s.top + s.height/2; const int BOTTOM = s.top + s.height; index = s.display_start - 1; for (i = 0; i < s.width; ++i) { previous_index = index; index = s.display_start + DISPLAY_WIDTH*i/s.width; highlighted = (previous_index < s.cursor) && (s.cursor <= index); selected = (s.select_start <= index) && (index <= s.select_end); x = s.left + i; dy = s.header.lpData[index]*s.height/254; if (highlighted) draw_line(x, TOP, CENTER, BOTTOM, dy, LIGHTRED, RED); else if (selected) draw_line(x, TOP, CENTER, BOTTOM, dy, LIGHTBLUE, BLUE); else draw_line(x, TOP, CENTER, BOTTOM, dy, LIGHTGRAY, DARKGRAY); } setcolor(s.current ? LIGHTGREEN:BLACK); rectangle(s.left-1, s.top-1, s.left+s.width+0, s.top+s.height+1); rectangle(s.left-2, s.top-2, s.left+s.width+1, s.top+s.height+2); rectangle(s.left-3, s.top-3, s.left+s.width+2, s.top+s.height+3); } /////////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////// void draw_line(int x, int top, int center, int bottom, int dy, int b, int f) // Draws one of the vertical lines for a waveform with an x-coordinate of x. // The backgrtound color of the line is b, and this background goes from // y=top down to y=bottom. On top of this is a shorter line in the foreground // color of f. This line goes from y=center to y=(center-dy). { setcolor(b); if (dy > 0) { line(x, top, x, center - dy - 1); line(x, bottom, x, center - 1); } else { line(x, bottom, x, center - dy + 1); line(x, top, x, center + 1); } setcolor(f); line(x, center, x, center - dy); } /////////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////// void execute(sound data[], char command) // This function performs some action on the sounds according the the command // that was given by the user. Later, I expect to maybe have more parameters, // but this will do for now. { int i = current(data); switch (command) { case 'C' - '@': // Ctrl-C: Copy current selection to clipboard copy_to_cb(data[i], 0, 0); break; case KEY_UP: // Change the current wave to the previous one data[i].current = false; draw(data[i]); i = (i + MANY_WAVES - 1) % MANY_WAVES; data[i].current = true; draw(data[i]); break; case KEY_DOWN: // Change the current wave to the next one data[i].current = false; draw(data[i]); i = (i + 1) % MANY_WAVES; data[i].current = true; draw(data[i]); break; case 'p': // Play the whole current wave play(data[i]); break; } } /////////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////// void initialize(sound& s, int index) // This function puts some initial sinewave sounds in a sound. { const int FREQUENCY = 440; const int SECONDS = 3; const int SAMPLE_RATE = 11025; const int BUFFER_SIZE = SECONDS * SAMPLE_RATE; int i; double angle = 0; fill_n((char *)(&s), sizeof(sound), 0); s.header.lpData = new char[BUFFER_SIZE]; s.header.dwBufferLength = BUFFER_SIZE; for (i = 0; i < BUFFER_SIZE; ++i) { s.header.lpData[i] = (char) (127 + 127 * sin(angle)); angle += 2 * M_PI * FREQUENCY * (index+1) / SAMPLE_RATE; } s.format.wFormatTag = WAVE_FORMAT_PCM; s.format.nChannels = 1; s.format.nSamplesPerSec = SAMPLE_RATE; s.format.nAvgBytesPerSec = SAMPLE_RATE; s.format.nBlockAlign = 1; s.format.wBitsPerSample = 8; s.format.cbSize = 0; s.display_start = 0; s.display_end = BUFFER_SIZE-1; s.select_start = -1; s.select_end = -1; s.cursor = 0; s.top = (WAVE_HEIGHT+VERTICAL_BORDER)*index + VERTICAL_BORDER; s.left = SIDE_BORDER; s.width = WAVE_WIDTH; s.height = WAVE_HEIGHT; s.current = (index == 0); } /////////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////// char next_request( ) // This function returns a single character that specifies the user's next // edit request. Note that the special characters such as KEY_UP correspond // to upper case letters, so each ordinary character (such as 'P') is // converted to a lower-case letter before it is returned. { char c; c = getch( ); if (c == 0) return getch( ); else return tolower(c); } /////////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////// void play(sound& s) // This function plays the sound on the sound card. { HWAVEOUT handle; MMTIME time; int save_cursor = s.cursor; waveOutOpen(&handle, WAVE_MAPPER, &(s.format), 0, 0, 0); waveOutPrepareHeader(handle, &(s.header), sizeof(WAVEHDR)); waveOutWrite(handle, &(s.header), sizeof(WAVEHDR)); time.wType = TIME_BYTES; do { waveOutGetPosition(handle, &time, sizeof(MMTIME)); s.cursor = time.u.cb; draw(s); } while (time.u.cb < s.header.dwBufferLength); waveOutClose(handle); s.cursor = save_cursor; draw(s); } ///////////////////////////////////////////////////////////////////////////////