449 lines
14 KiB
C
449 lines
14 KiB
C
|
/* (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 <windows.h>
|
||
|
#include <windowsx.h>
|
||
|
#include <mmsystem.h>
|
||
|
#include <mmreg.h>
|
||
|
#include <math.h>
|
||
|
#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 <gpWaveSamples> to 8 bits
|
||
|
short * piSrc = NULL; // pointer into <gpWaveSamples> to 16 bits
|
||
|
// (use one or other according to wave format)
|
||
|
|
||
|
int cbSrc; // number of samples that can be copied
|
||
|
BYTE * pbDst; // pointer into <gpbWaveDisplay>
|
||
|
int cbDst; // size of <gpWaveDisplay>
|
||
|
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 <gpbWaveDisplay> */
|
||
|
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 <gpbWaveDisplay> */
|
||
|
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 <gpbWaveDisplay> */
|
||
|
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<dx; i++)
|
||
|
{
|
||
|
n = (BYTE)gpbWaveDisplay[i]; // n.b. must get it UNSIGNED
|
||
|
n = n-128; // -16..15
|
||
|
|
||
|
if (n > 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);
|
||
|
}
|