/* (C) Copyright Microsoft Corporation 1991. All Rights Reserved */ /* wavedisp.c * * Implements waveform display control ("td_wavedisplay"). * * This is NOT a general-purpose control (see the globals below). * * The waveform is represented as a string of characters in a font that * consists of vertical lines that correspond to different amplitudes. * Actually there is no font, it's just done with patBlts * * WARNING: this control cheats: it stores information in globals, so you * couldn't put it in a DLL (or use two of them in the same app) * without changing it. */ /* Revision History. * 4/2/92 LaurieGr (AKA LKG) Ported to WIN32 / WIN16 common code * 25/6/92LaurieGr enhanced, Also had to reformat to 80 cols because * NT has only crappy fonts again. * 21/Feb/94 LaurieGr merged Daytona and Motown versions */ #include "nocrap.h" #include #include #include #include #include #include "SoundRec.h" /* constants */ #define MAX_TRIGGER_SEARCH 200 // limit search for trigger pt. #define MIN_TRIGGER_SAMPLE (128 - 8) // look for silent part #define MAX_TRIGGER_SAMPLE (128 + 8) // look for silent part #define MIN_TRIG16_SAMPLE (-1024) // look for silent part #define MAX_TRIG16_SAMPLE (1024) // look for silent part /* globals */ static NPBYTE gpbWaveDisplay; // text string in WaveLine font // initially has samples in it // enough room for 4 bytes/sample static RECT grcWaveDisplay; // wave display rectangle static HBITMAP ghbmWaveDisplay; // mono bitmap. static HDC ghdcWaveDisplay; // memory DC for bitmap. // static iXScale = 1; // samples per pel across screen /* UpdateWaveDisplayString() * * Copy samples from just before the current position in the sample buffer * to the wave display string. The code tries to find a good "trigger point" * in the waveform so that the waveform will be aligned at the beginning of * a wave. * * The current position is in gpWaveSamples at glWavePosition which is * measured in samples (not bytes). * * The wave display string will contain numbers in the range -16..15 * * for 8 bit: x' = abs(x-128)/8 * for 16 bit: x' = abs(x)/2048 * * When the display is "in motion" (i.e. actually playing or recording * we try to keep the display somewhat static by looking for a trigger * point (like an oscilloscope would) and display that part of the wave * that has just either played or been recorded. * */ static void NEAR PASCAL UpdateWaveDisplayString(void) { // piSrc and pbSrc are init NULL to kill a compiler diagnostic. // The compiler cannot follow the logic and thinks that they may be // used before being set. (It's wrong. Hint: look at cbSrc and cbTrigger) BYTE * pbSrc = NULL; // pointer into to 8 bits short * piSrc = NULL; // pointer into to 16 bits // (use one or other according to wave format) int cbSrc; // number of samples that can be copied BYTE * pbDst; // pointer into int cbDst; // size of int cbTrigger; // limit the search for a "trigger" BYTE b; int i; int cnt; WORD nSkipChannels; BOOL fStereoIn; BOOL fEightIn; cbDst = grcWaveDisplay.right - grcWaveDisplay.left; // rectangle size pbDst = gpbWaveDisplay; // Note: IsWaveFormatPCM() is called before this function is ever called, therefore // we can always rely on the fact that gpWaveFormat->wwFormatTag == WAVE_FORMAT_PCM. // This also implies, as mentioned in the docs on WAVEFORMATEX, that the // gpWaveFormat->wBitsPerSample should be equal to 8 or 16. // Note: We average the first two channels if they exist, any additional channels are // ignored. fStereoIn = gpWaveFormat->nChannels != 1; fEightIn = ((LPWAVEFORMATEX)gpWaveFormat)->wBitsPerSample == 8; nSkipChannels = max (0, gpWaveFormat->nChannels - 2); /* search for a "trigger point" if we are recording or playing */ if ((ghWaveOut != NULL) || (ghWaveIn != NULL)) { // we are in motion - align the *right* hand side of the window. cbTrigger = MAX_TRIGGER_SEARCH; if (gpWaveSamples == NULL) { /* no document at all is open */ cbSrc = 0; } else { long lStartOffsetSrc, lEndOffsetSrc; /* align the *right* side of wave display to the current * position in the wave buffer, so that during recording * we see only samples that have just been recorded */ lStartOffsetSrc = glWavePosition - (cbDst + cbTrigger); lEndOffsetSrc = glWavePosition; if (lStartOffsetSrc < 0) lEndOffsetSrc -= lStartOffsetSrc, lStartOffsetSrc = 0L; if (lEndOffsetSrc > glWaveSamplesValid) lEndOffsetSrc = glWaveSamplesValid; // Bombay Bug 1360: lStartOffsetSrc > lEndOffsetSrc causes GP Fault // if glWaveSamplesValid < lStartOffsetSrc we have a problem. if (lStartOffsetSrc > lEndOffsetSrc) { lStartOffsetSrc = lEndOffsetSrc - (cbDst + cbTrigger); if (lStartOffsetSrc < 0) lStartOffsetSrc = 0L; } cbSrc = (int)wfSamplesToBytes(gpWaveFormat, lEndOffsetSrc - lStartOffsetSrc); /* copy samples from buffer into local one */ memmove( gpbWaveDisplay , gpWaveSamples + wfSamplesToBytes(gpWaveFormat, lStartOffsetSrc) , cbSrc ); pbSrc = (BYTE *) gpbWaveDisplay; piSrc = (short *) gpbWaveDisplay; } if (cbTrigger > 0) { cbTrigger = min(cbSrc, cbTrigger); // don't look beyond buffer end /* search for a silent part in waveform */ if (fEightIn) { while (cbTrigger > 0) { b = *pbSrc; if ((b > MIN_TRIGGER_SAMPLE) && (b < MAX_TRIGGER_SAMPLE)) break; cbSrc--, pbSrc++, cbTrigger--; if (fStereoIn) pbSrc+=(nSkipChannels+1); } } else { // not EightIn while (cbTrigger > 0) { i = *piSrc; if ((i > MIN_TRIG16_SAMPLE) && (i < MAX_TRIG16_SAMPLE)) break; cbSrc--, piSrc++, cbTrigger--; if (fStereoIn) piSrc+=(nSkipChannels+1); } } /* search for a non-silent part in waveform (this is the "trigger") */ if (fEightIn) { while (cbTrigger > 0) { b = *pbSrc; if ((b <= MIN_TRIGGER_SAMPLE) || (b >= MAX_TRIGGER_SAMPLE)) break; cbSrc--, pbSrc++, cbTrigger--; if (fStereoIn) pbSrc+=(nSkipChannels+1); } } else { // not EightIn while (cbTrigger > 0) { i = *piSrc; if ((i <= MIN_TRIG16_SAMPLE) || (i >= MAX_TRIG16_SAMPLE)) break; cbSrc--, piSrc++, cbTrigger--; if (fStereoIn) piSrc+=(nSkipChannels+1); } } } } else // it's not playing or recording - static display { long lStartOffsetSrc, lEndOffsetSrc; /* align the *left* side of wave display to the current * position in the wave buffer */ lStartOffsetSrc = glWavePosition; lEndOffsetSrc = glWavePosition + cbDst; if (lEndOffsetSrc > glWaveSamplesValid) lEndOffsetSrc = glWaveSamplesValid; cbSrc = (int)wfSamplesToBytes( gpWaveFormat , lEndOffsetSrc - lStartOffsetSrc ); // // copy samples from buffer into local one // memmove( gpbWaveDisplay , gpWaveSamples + wfSamplesToBytes(gpWaveFormat, lStartOffsetSrc) , cbSrc ); pbSrc = (BYTE *) gpbWaveDisplay; piSrc = (short *) gpbWaveDisplay; } cnt = min(cbSrc, cbDst); cbDst -= cnt; /* map cnt number of samples from pbSrc to string characters at pbDst ** fEightIn => 8 byte samples, else 16 ** fStereoIn => Average left and right channels ** ** pbSrc and pbDst both point into the same buffer addressed by ** gpbWaveDisplay, pbSrc >= pbDst. We process left to right, so OK. */ if (fEightIn) { BYTE *pbDC = pbDst; int dccnt = cnt; DWORD dwSum = 0L; while (cnt-- > 0) { b = *pbSrc++; if (fStereoIn) { // Average left and right channels. b /= 2; b += (*pbSrc++ / 2); // Skip channels past Stereo pbSrc+=nSkipChannels; } dwSum += *pbDst++ = (BYTE)(b/8 + 112); // 128 + (b-128)/8 } /* Eliminate DC offsets by subtracting the average offset * over all samples. */ if (dwSum) { dwSum /= (DWORD)dccnt; dwSum -= 128; while (dwSum && dccnt-- > 0) *pbDC++ -= (BYTE)dwSum; } } else { BYTE *pbDC = pbDst; int dccnt = cnt; LONG lSum = 0L; while (cnt-- > 0) { i = *piSrc++; if (fStereoIn) { // Average left and right channels. i /= 2; i += (*piSrc++ / 2); // Skip channels past Stereo piSrc+=nSkipChannels; } lSum += *pbDst++ = (BYTE)(i/2048 + 128); } /* Eliminate DC offsets by subtracting the average offset * over all samples. */ if (lSum) { lSum /= dccnt; lSum -= 128; while (lSum && dccnt-- > 0) *pbDC++ -= (BYTE)lSum; } } /* if necessary, pad the strings with whatever character represents * the "silence level". This is 128, the midpoint level. */ while (cbDst-- > 0) *pbDst++ = 128; } /* WaveDisplayWndProc() * * This is the window procedure for the "WaveDisplay" control. */ INT_PTR CALLBACK WaveDisplayWndProc(HWND hwnd, UINT wMsg, WPARAM wParam, LPARAM lParam) { PAINTSTRUCT ps; RECT rc; int i; int n; int dx; int dy; switch (wMsg) { case WM_CREATE: /* make the window a bit bigger so that it lines up with * the frames of the shadow-frames beside it */ /* allocate */ GetClientRect(hwnd, &grcWaveDisplay); InflateRect(&grcWaveDisplay, -1, -1); // account for border gpbWaveDisplay = (NPBYTE)GlobalAllocPtr(GHND, (grcWaveDisplay.right+MAX_TRIGGER_SEARCH) * 4); // 4 is the maximum bytes per sample allowed if (gpbWaveDisplay == NULL) return -1; // out of memory ghdcWaveDisplay = CreateCompatibleDC(NULL); if (ghdcWaveDisplay == NULL) return -1; // out of memory ghbmWaveDisplay = CreateBitmap( grcWaveDisplay.right-grcWaveDisplay.left, grcWaveDisplay.bottom-grcWaveDisplay.top, 1,1,NULL); if (ghbmWaveDisplay == NULL) return -1; // out of memory SelectObject(ghdcWaveDisplay, ghbmWaveDisplay); break; case WM_DESTROY: /* free */ if (gpbWaveDisplay != NULL) { GlobalFreePtr(gpbWaveDisplay); gpbWaveDisplay = NULL; } if (ghbmWaveDisplay != NULL) { DeleteDC(ghdcWaveDisplay); DeleteObject(ghbmWaveDisplay); ghdcWaveDisplay = NULL; ghbmWaveDisplay = NULL; } break; case WM_ERASEBKGND: /* draw the border and fill */ GetClientRect(hwnd, &rc); DrawShadowFrame((HDC)wParam, &rc); return 0L; case WM_PAINT: BeginPaint(hwnd, &ps); if (!IsWaveFormatPCM(gpWaveFormat)) { FillRect(ps.hdc, &grcWaveDisplay, ghbrPanel); } else if (gpbWaveDisplay != NULL) { /* update */ UpdateWaveDisplayString(); dx = grcWaveDisplay.right-grcWaveDisplay.left; dy = grcWaveDisplay.bottom-grcWaveDisplay.top; // // update the bitmap. // PatBlt(ghdcWaveDisplay,0,0,dx,dy,BLACKNESS); PatBlt(ghdcWaveDisplay,0,dy/2,dx,1,WHITENESS); for (i=0; i 0) PatBlt(ghdcWaveDisplay, i, dy/2-n, 1, n*2+1, WHITENESS); if (n < -1) { n++; // neg peak == pos peak PatBlt(ghdcWaveDisplay, i, dy/2+n, 1, -(n*2)+1, WHITENESS); } } /* draw the waveform */ SetTextColor(ps.hdc, RGB_BGWAVEDISP); SetBkColor(ps.hdc, RGB_FGWAVEDISP); BitBlt(ps.hdc, grcWaveDisplay.left, grcWaveDisplay.top, dx,dy, ghdcWaveDisplay, 0, 0, SRCCOPY); } EndPaint(hwnd, &ps); return 0L; } return DefWindowProc(hwnd, wMsg, wParam, lParam); }