Scope graphing rewrite: speech/music merged, allows 8- or 16-bit, auto-adjust level; bug #1064

git-svn-id: svn://svn.code.sf.net/p/sc2/code/trunk@3181 8092fc87-c524-0410-9efc-e669fe64eaf9
This commit is contained in:
avolkov
2009-09-30 22:37:36 +00:00
parent 3c22948d78
commit 9925a29c89
9 changed files with 213 additions and 182 deletions
+2
View File
@@ -1,4 +1,6 @@
Changes towards version 0.7: Changes towards version 0.7:
- Allow any sound data format to be graphed by comm oscilloscope; also
auto-adjust the scope for different gain levels (bug #1064) - Alex
- Game settings Quit menu now delegates to F10 quit (bug #462) - Alex - Game settings Quit menu now delegates to F10 quit (bug #462) - Alex
- Do not pause the game in places where not relevant (bug #984) - Alex - Do not pause the game in places where not relevant (bug #984) - Alex
- Fixed crashes and potential weirdness when loading savegames from - Fixed crashes and potential weirdness when loading savegames from
+28
View File
@@ -237,3 +237,31 @@ audio_BufferData (audio_Object bufobj, uint32 format, void* data,
audiodrv.BufferData (bufobj, audiodrv.EnumLookup[format], data, size, audiodrv.BufferData (bufobj, audiodrv.EnumLookup[format], data, size,
freq); freq);
} }
bool
audio_GetFormatInfo (uint32 format, int *channels, int *sample_size)
{
switch (format)
{
case audio_FORMAT_MONO8:
*channels = 1;
*sample_size = sizeof (uint8);
return true;
case audio_FORMAT_STEREO8:
*channels = 2;
*sample_size = sizeof (uint8);
return true;
case audio_FORMAT_MONO16:
*channels = 1;
*sample_size = sizeof (sint16);
return true;
case audio_FORMAT_STEREO16:
*channels = 2;
*sample_size = sizeof (sint16);
return true;
}
return false;
}
+2
View File
@@ -164,4 +164,6 @@ void audio_GetBufferi (audio_Object bufobj, audio_BufferProp pname,
void audio_BufferData (audio_Object bufobj, uint32 format, void* data, void audio_BufferData (audio_Object bufobj, uint32 format, void* data,
uint32 size, uint32 freq); uint32 size, uint32 freq);
bool audio_GetFormatInfo (uint32 format, int *channels, int *sample_size);
#endif /* _AUDIOCORE_H */ #endif /* _AUDIOCORE_H */
+3 -3
View File
@@ -75,11 +75,11 @@ typedef struct tfb_soundsource
sint32 start_time; sint32 start_time;
void *positional_object; void *positional_object;
// for oscilloscope // Cyclic waveform buffer for oscilloscope
void *sbuffer; void *sbuffer;
uint32 sbuf_start; uint32 sbuf_start; // cyclic buffer tail (confusing, eh?)
uint32 sbuf_size; uint32 sbuf_size;
uint32 sbuf_offset; uint32 sbuf_offset; // cyclic buffer head
uint32 sbuf_lasttime; uint32 sbuf_lasttime;
// keep track for paused tracks // keep track for paused tracks
uint32 pause_time; uint32 pause_time;
+172
View File
@@ -449,3 +449,175 @@ StreamDecoderTaskFunc (void *data)
return 0; return 0;
} }
inline sint32
readSoundSample (void *ptr, int sample_size)
{
if (sample_size == sizeof (uint8))
return (*(uint8*)ptr - 128) << 8;
else
return *(sint16*)ptr;
}
// Graphs the current sound data for the oscilloscope.
// Includes a rudimentary automatic gain control (AGC) to properly graph
// the streams at different gain levels (based on running average).
// We use AGC because different pieces of music and speech can easily be
// at very different gain levels, because the game is moddable.
int
GraphForegroundStream (uint8 *data, sint32 width, sint32 height)
{
int source_num;
TFB_SoundSource *source;
TFB_SoundDecoder *decoder;
int channels;
int sample_size;
int full_sample;
int step;
long played_time;
long delta;
uint8 *sbuffer;
unsigned long pos;
int scale;
sint32 i;
// AGC variables
#define DEF_PAGE_MAX 28000
#define AGC_PAGE_COUNT 16
static int page_sum = DEF_PAGE_MAX * AGC_PAGE_COUNT;
static int pages[AGC_PAGE_COUNT] =
{
DEF_PAGE_MAX, DEF_PAGE_MAX, DEF_PAGE_MAX, DEF_PAGE_MAX,
DEF_PAGE_MAX, DEF_PAGE_MAX, DEF_PAGE_MAX, DEF_PAGE_MAX,
DEF_PAGE_MAX, DEF_PAGE_MAX, DEF_PAGE_MAX, DEF_PAGE_MAX,
DEF_PAGE_MAX, DEF_PAGE_MAX, DEF_PAGE_MAX, DEF_PAGE_MAX,
};
static int page_head;
#define AGC_FRAME_COUNT 8
static int frame_sum;
static int frames;
static int avg_amp = DEF_PAGE_MAX; // running amplitude (sort of) average
int target_amp;
int max_a;
#define VAD_MIN_ENERGY 100
long energy;
if (speechVolumeScale != 0.0f)
{ // Use speech waveform when speech is enabled
source_num = SPEECH_SOURCE;
// Step is picked experimentally. Using step of 1 sample at 11025Hz,
// because human speech is mostly in the low frequencies, and it looks
// better this way.
step = 1;
}
else if (musicVolumeScale != 0.0f)
{ // Use music waveform when speech is disabled
source_num = MUSIC_SOURCE;
// Step is picked experimentally. Using step of 4 samples at 11025Hz.
// It looks better this way.
step = 4;
}
else
{
return 0;
}
source = &soundSource[source_num];
LockMutex (source->stream_mutex);
if (!PlayingStream (source_num) || !source->sample
|| !source->sample->decoder || !source->sbuffer
|| source->sbuf_size == 0)
{ // We don't have data to return, oh well.
UnlockMutex (source->stream_mutex);
return 0;
}
decoder = source->sample->decoder;
assert (audio_GetFormatInfo (decoder->format, &channels, &sample_size));
full_sample = channels * sample_size;
// See how far into the buffer we should be now
played_time = GetTimeCounter () - source->sbuf_lasttime;
delta = played_time * decoder->frequency * full_sample / ONE_SECOND;
// align delta to sample start
delta = delta & ~(full_sample - 1);
if (delta < 0)
{
log_add (log_Debug, "GraphForegroundStream(): something is messed"
" with timing, delta %ld", delta);
delta = 0;
}
else if (delta > (long)source->sbuf_size)
{ // Stream decoder task has just had a heart attack, not much we can do
delta = 0;
}
// Step is in 11025 Hz units, so we need to adjust to source frequency
step = decoder->frequency * step / 11025;
if (step == 0)
step = 1;
step *= full_sample;
sbuffer = source->sbuffer;
pos = source->sbuf_offset + delta;
// We are not basing the scaling factor on signal energy, because we
// want it to *look* pretty instead of sounding nice and even
target_amp = (height >> 1) >> 1;
scale = avg_amp / target_amp;
max_a = 0;
energy = 0;
for (i = 0; i < width; ++i, pos += step)
{
sint32 s;
int t;
pos %= source->sbuf_size;
s = readSoundSample (sbuffer + pos, sample_size);
if (channels > 1)
s += readSoundSample (sbuffer + pos + sample_size, sample_size);
energy += (s * s) / 0x10000;
t = abs(s);
if (t > max_a)
max_a = t;
s = (s / scale) + (height >> 1);
if (s < 0)
s = 0;
else if (s > height - 1)
s = height - 1;
data[i] = s;
}
energy /= width;
// Very basic VAD. We don't want to count speech pauses in the average
if (energy > VAD_MIN_ENERGY)
{
// Record the maximum amplitude (sort of)
frame_sum += max_a;
++frames;
if (frames == 8)
{ // Got a full page
frame_sum /= AGC_FRAME_COUNT;
// Record the page
page_sum -= pages[page_head];
page_sum += frame_sum;
pages[page_head] = frame_sum;
page_head = (page_head + 1) % AGC_PAGE_COUNT;
frame_sum = 0;
frames = 0;
avg_amp = page_sum / AGC_PAGE_COUNT;
}
}
UnlockMutex (source->stream_mutex);
return 1;
}
+2
View File
@@ -30,4 +30,6 @@ TFB_SoundTag* FindTaggedBuffer (TFB_SoundSample* sample, audio_Object buffer);
void TFB_ClearBufferTag (TFB_SoundTag* ptag); void TFB_ClearBufferTag (TFB_SoundTag* ptag);
void TFB_TagBuffer (TFB_SoundSample* sample, audio_Object buffer, void* data); void TFB_TagBuffer (TFB_SoundSample* sample, audio_Object buffer, void* data);
int GraphForegroundStream (uint8 *data, sint32 width, sint32 height);
#endif #endif
-175
View File
@@ -19,9 +19,7 @@
#include "libs/sound/trackint.h" #include "libs/sound/trackint.h"
#include "libs/log.h" #include "libs/log.h"
#include "comm.h" #include "comm.h"
#include "sis.h"
#include "options.h" #include "options.h"
#include <assert.h>
#include <ctype.h> #include <ctype.h>
#include <stdlib.h> #include <stdlib.h>
@@ -821,179 +819,6 @@ FastForward_Page ()
return TRUE; return TRUE;
} }
// processes sound data to oscilloscope
int
GetSoundData (void *data)
{
// XXX: These two variants are begging to be merged
if (speechVolumeScale != 0.0f)
{
// speech is enabled
LockMutex (soundSource[SPEECH_SOURCE].stream_mutex);
if (soundSource[SPEECH_SOURCE].sample && soundSource[SPEECH_SOURCE].sample->decoder &&
PlayingStream (SPEECH_SOURCE) && soundSource[SPEECH_SOURCE].sbuffer &&
soundSource[SPEECH_SOURCE].sbuf_size > 0)
{
float played_time = (GetTimeCounter () - soundSource[SPEECH_SOURCE].sbuf_lasttime) /
(float)ONE_SECOND;
long delta = (int) (played_time * (float)soundSource[SPEECH_SOURCE].
sample->decoder->frequency * 2.0f);
unsigned long pos;
int i;
int step;
UBYTE *scopedata = (UBYTE *) data;
UBYTE *sbuffer = soundSource[SPEECH_SOURCE].sbuffer;
assert (soundSource[SPEECH_SOURCE].sample->decoder->frequency >= 11025);
assert (soundSource[SPEECH_SOURCE].sample->decoder->format == audio_FORMAT_MONO16);
// Using step of 1 sample at 11025Hz, because the human speech
// is mostly in the low frequencies
step = soundSource[SPEECH_SOURCE].sample->decoder->frequency * 2 / 11025;
step = (step + 1) & ~1;
if (delta < 0)
{
log_add (log_Debug, "GetSoundData(): something's messed"
" with timing, delta %ld", delta);
delta = 0;
}
else if (delta > (int)(soundSource[SPEECH_SOURCE].sbuf_size * 2))
{
#if 0
log_add (log_Debug, "GetSoundData(): something's messed"
" with timing, delta %d", delta);
#endif
delta = 0;
}
#if 0
log_add (log_Debug, "played_data %d total_decoded %d delta %d",
played_data, soundSource[SPEECH_SOURCE].total_decoded,
delta);
#endif
pos = soundSource[SPEECH_SOURCE].sbuf_offset + delta;
if (pos % 2 == 1)
pos++;
// pos is an unsigned data type; this assertion cannot fail!
// assert (pos >= 0);
for (i = 0; i < RADAR_WIDTH - 2; ++i)
{
SDWORD s;
for (;;)
{
if (pos >= soundSource[SPEECH_SOURCE].sbuf_size)
pos = pos - soundSource[SPEECH_SOURCE].sbuf_size;
else
break;
}
s = *(SWORD*) (&sbuffer[pos]);
s = (s / 1360) + (RADAR_HEIGHT >> 1);
if (s < 1)
s = 1;
else if (s > RADAR_HEIGHT - 2)
s = RADAR_HEIGHT - 2;
scopedata[i] = (UBYTE) s;
pos += step;
}
UnlockMutex (soundSource[SPEECH_SOURCE].stream_mutex);
return 1;
}
UnlockMutex (soundSource[SPEECH_SOURCE].stream_mutex);
}
else if (musicVolumeScale != 0.0f)
{
// speech is disabled but music is not so process it instead
LockMutex (soundSource[MUSIC_SOURCE].stream_mutex);
if (soundSource[MUSIC_SOURCE].sample && soundSource[MUSIC_SOURCE].sample->decoder &&
PlayingStream (MUSIC_SOURCE) && soundSource[MUSIC_SOURCE].sbuffer &&
soundSource[MUSIC_SOURCE].sbuf_size > 0)
{
float played_time = (GetTimeCounter () - soundSource[MUSIC_SOURCE].sbuf_lasttime) /
(float)ONE_SECOND;
int delta = (int) (played_time * (float)soundSource[MUSIC_SOURCE].
sample->decoder->frequency * 4.0f);
unsigned long pos;
int i, step;
UBYTE *scopedata = (UBYTE *) data;
UBYTE *sbuffer = soundSource[MUSIC_SOURCE].sbuffer;
assert (soundSource[MUSIC_SOURCE].sample->decoder->frequency >= 11025);
assert (soundSource[MUSIC_SOURCE].sample->decoder->format == audio_FORMAT_STEREO16);
// Using step of 4 samples at 11025Hz for the music
step = soundSource[MUSIC_SOURCE].sample->decoder->frequency / 11025 * 16;
if (step % 2 == 1)
step++;
if (delta < 0)
{
#if 0
log_add (log_Debug, "GetSoundData(): something's messed"
" with timing, delta %d", delta);
#endif
delta = 0;
}
else if (delta > (int)(soundSource[MUSIC_SOURCE].sbuf_size * 2))
{
#if 0
log_add (log_Debug, "GetSoundData(): something's messed"
" with timing, delta %d", delta);
#endif
delta = 0;
}
#if 0
log_add (log_Debug, "played_data %d total_decoded %d delta %d",
played_data, soundSource[MUSIC_SOURCE].total_decoded,
delta);
#endif
pos = soundSource[MUSIC_SOURCE].sbuf_offset + delta;
if (pos % 2 == 1)
pos++;
// pos is an unsigned type; this assertion cannot fail!
// assert (pos >= 0);
for (i = 0; i < RADAR_WIDTH - 2; ++i)
{
SDWORD s;
for (;;)
{
if (pos >= soundSource[MUSIC_SOURCE].sbuf_size)
pos = pos - soundSource[MUSIC_SOURCE].sbuf_size;
else
break;
}
s = (*(SWORD*)(&sbuffer[pos])) + (*(SWORD*)(&sbuffer[pos + 2]));
s = (s / 1800) + (RADAR_HEIGHT >> 1);
if (s < 1)
s = 1;
else if (s > RADAR_HEIGHT - 2)
s = RADAR_HEIGHT - 2;
scopedata[i] = (UBYTE) s;
pos += step;
}
UnlockMutex (soundSource[MUSIC_SOURCE].stream_mutex);
return 1;
}
UnlockMutex (soundSource[MUSIC_SOURCE].stream_mutex);
}
return 0;
}
// tells current position of streaming speech // tells current position of streaming speech
int int
GetSoundInfo (int max_len) GetSoundInfo (int max_len)
-1
View File
@@ -37,7 +37,6 @@ void FastReverse_Page(void);
void StopTrack(void); void StopTrack(void);
void SpliceTrack(UNICODE *filespec, UNICODE *textspec, UNICODE *TimeStamp, TFB_TrackCB cb); void SpliceTrack(UNICODE *filespec, UNICODE *textspec, UNICODE *TimeStamp, TFB_TrackCB cb);
void SpliceMultiTrack (UNICODE *TrackNames[], UNICODE *TrackText); void SpliceMultiTrack (UNICODE *TrackNames[], UNICODE *TrackText);
int GetSoundData (void *data);
int GetSoundInfo (int max_len); int GetSoundInfo (int max_len);
#endif #endif
+4 -3
View File
@@ -21,6 +21,7 @@
#include "sis.h" #include "sis.h"
#include "libs/graphics/gfx_common.h" #include "libs/graphics/gfx_common.h"
#include "libs/graphics/drawable.h" #include "libs/graphics/drawable.h"
#include "libs/sound/sound.h"
#include "libs/sound/trackplayer.h" #include "libs/sound/trackplayer.h"
@@ -92,15 +93,15 @@ Oscilloscope (DWORD grab_data)
return; return;
TFB_DrawImage_Image (scope_bg, 0, 0, 0, NULL, scope_surf); TFB_DrawImage_Image (scope_bg, 0, 0, 0, NULL, scope_surf);
if (GetSoundData (scope_data)) if (GraphForegroundStream (scope_data, RADAR_WIDTH - 2, RADAR_HEIGHT - 2))
{ {
int i, r, g, b; int i, r, g, b;
TFB_DrawCanvas_GetPixel (scope_bg->NormalImg, TFB_DrawCanvas_GetPixel (scope_bg->NormalImg,
scope_bg->extent.width / 2, scope_bg->extent.height / 2, scope_bg->extent.width / 2, scope_bg->extent.height / 2,
&r, &g, &b); &r, &g, &b);
for (i = 0; i < RADAR_WIDTH - 3; ++i) for (i = 0; i < RADAR_WIDTH - 3; ++i)
TFB_DrawImage_Line (i + 1, scope_data[i], i + 2, TFB_DrawImage_Line (i + 1, scope_data[i] + 1, i + 2,
scope_data[i + 1], r, g, b, scope_surf); scope_data[i + 1] + 1, r, g, b, scope_surf);
} }
TFB_DrawImage_Image (scope_surf, 0, 0, 0, NULL, scope_frame->image); TFB_DrawImage_Image (scope_surf, 0, 0, 0, NULL, scope_frame->image);