|
/* This class has been written by
* Corinna John (Hannover, Germany)
* cj@binary-universe.net
*
* The GPL (GNU General public license)
* is valid for this file. Other files of the project
* may be free of specific licenses, but this one will
* be used in other GPL projects.
*
* Please send me a little feedback about what you're
* using the code for and what changes you'd like to
* see in later versions.
*/
using System;
using System.IO;
using System.Drawing;
using System.Drawing.Imaging;
using System.Diagnostics;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Text;
using System.Windows.Forms;
namespace WaveMixer
{
/// <summary>Encapsulates the colour flags for visualizing a wave channel.</summary>
public class ChannelColorFlags
{
private bool isRed;
private bool isGreen;
private bool isBlue;
public bool IsRed
{
get { return isRed; }
set { isRed = value; }
}
public bool IsBlue
{
get { return isBlue; }
set { isBlue = value; }
}
public bool IsGreen
{
get { return isGreen; }
set { isGreen = value; }
}
}
/// <summary>Stores the colours of a pixel.</summary>
public struct PixelData
{
public byte Blue;
public byte Green;
public byte Red;
}
/// <summary>Edits a PCM wave sound.</summary>
public class WaveUtility
{
/// <summary>Adds a sine sound of a specific frequency.</summary>
/// <param name="waveSound">The sound that's being edited</param>
/// <param name="frequencyHz">Frequency of the new wave in Hertz.</param>
/// <param name="offsetSeconds">Starting second of the new wave.</param>
/// <param name="lengthSeconds">Length of the new wave in seconds.</param>
/// <param name="amplitude">Maximum amplitude of the new wave.</param>
public void AddWave(WaveSound waveSound, float frequencyHz, float offsetSeconds, float lengthSeconds, int amplitude)
{
//existing wave samples
short[] samples = waveSound.Samples;
//interval for 1 Hz
double xStep = (2 * Math.PI) / waveSound.Format.SamplesPerSec;
//interval for the requested frequency = 1Hz * frequencyHz
xStep = xStep * frequencyHz;
long lastSample;
double xValue = 0;
short yValue;
short channelSample;
long offsetSamples = (long)(waveSound.Format.Channels * waveSound.Format.SamplesPerSec * offsetSeconds);
if (offsetSamples < samples.Length) //if the beginning sample exists
{
lastSample = (long)(waveSound.Format.Channels * waveSound.Format.SamplesPerSec * (offsetSeconds + lengthSeconds));
if (lastSample > samples.Length)
{ //last sample does not exist - shorten the new wave
lastSample = samples.Length - waveSound.Format.Channels + 1;
}
for (long n = offsetSamples; n < lastSample; n += waveSound.Format.Channels)
{
xValue += xStep;
yValue = (short)(Math.Sin(xValue) * amplitude);
for (int channelIndex = 0; channelIndex < waveSound.Format.Channels; channelIndex++)
{
channelSample = samples[n + channelIndex];
channelSample = (short)((channelSample + yValue) / 2);
samples[n + channelIndex] = channelSample;
}
}
}
}
/// <summary>Changes the volume of a sound.</summary>
/// <param name="waveSound">The sound that's being edited.</param>
/// <param name="maxSampleValue">Current maximum amplitude.</param>
/// <param name="newMaximum">New maximum amplitude. May not be greater than 127, otherwise the result will be useless.</param>
public void ChangeAmplitude(WaveSound waveSound, short maxSampleValue, short newMaximum)
{
float scale = (float)newMaximum / maxSampleValue;
for (int waveIndex = 0; waveIndex < waveSound.Count; waveIndex++)
{
waveSound[waveIndex] = (short)(waveSound[waveIndex] * scale);
}
}
/// <summary>Paints a bitmap for a wave.</summary>
/// <param name="waveSound">The sound you want to paint.</param>
/// <param name="width">Width of the bitmap.</param>
/// <param name="height">Height of the bitmap.</param>
/// <param name="firstChannelColor">Colors to use for the first channel.</param>
/// <param name="secondChannelColor">Colors to use for the seconds channel.</param>
/// <param name="maximizeVolume">Whether or not to paint the sound with maximized amplitude == best contrast.</param>
/// <param name="maxSampleValue">Value of the loudest sample. Can be zero if [maximizeVolume] is false.</param>
/// <returns>Picture of the sound.</returns>
public unsafe Bitmap PaintWave(WaveSound waveSound, int width, int height, ChannelColorFlags firstChannelColor, ChannelColorFlags secondChannelColor, bool convertSignedToAbsoluteValues, bool maximizeVolume, short maxSampleValue)
{
Bitmap bmp = new Bitmap(width, height, PixelFormat.Format24bppRgb);
BitmapData bitmapData = bmp.LockBits(
new Rectangle(0, 0, width, height),
ImageLockMode.ReadOnly, PixelFormat.Format24bppRgb);
int stride = bitmapData.Stride;
int wastedPixels = stride - width*3;
PixelData* pPixel = (PixelData*)bitmapData.Scan0.ToPointer();
try
{
ChannelColorFlags[] channelColors = new ChannelColorFlags[2] { firstChannelColor, secondChannelColor };
Color color;
int pixelColumnIndex = 0;
short currentSample;
short currentChannelSample;
short[] channelPartMaximum = new short[waveSound.Format.Channels];
short[] channelPartMinimum = new short[waveSound.Format.Channels];
float maximizeScale = 127f / maxSampleValue;
if (maximizeVolume)
{
maximizeScale = maximizeScale * short.MaxValue / maxSampleValue;
}
int countPixels = width * height;
int samplesPerPixel = 1;
if (countPixels < waveSound.Count)
{
samplesPerPixel = (int)Math.Ceiling((double)waveSound.Count / countPixels);
}
int maxIndex = waveSound.Count - waveSound.Format.Channels;
int maxCountChannels = (waveSound.Format.Channels == 1) ? 1 : 2;
for (int waveIndex = 0; waveIndex < maxIndex; waveIndex += (samplesPerPixel * waveSound.Format.Channels))
{
//find the loudest sample that will be painted in the current pixel
for (int sampleIndex = waveIndex; sampleIndex < (waveIndex + samplesPerPixel) && sampleIndex < waveSound.Count; sampleIndex += waveSound.Format.Channels)
{
currentSample = waveSound[sampleIndex];
for (int channelIndex = 0; channelIndex < waveSound.Format.Channels; channelIndex++)
{
channelPartMaximum[channelIndex] = 0;
channelPartMinimum[channelIndex] = 0;
}
for (int channelIndex = 0; channelIndex < waveSound.Format.Channels; channelIndex++)
{
if (currentSample > channelPartMaximum[channelIndex]) { channelPartMaximum[channelIndex] = currentSample; }
if (currentSample < channelPartMinimum[channelIndex]) { channelPartMinimum[channelIndex] = currentSample; }
}
}
for (int channelIndex = 0; channelIndex < maxCountChannels; channelIndex++)
{
currentChannelSample = (channelPartMaximum[channelIndex] > channelPartMinimum[channelIndex] * -1)
? channelPartMaximum[channelIndex]
: channelPartMinimum[channelIndex];
currentChannelSample = (sbyte)(currentChannelSample * maximizeScale);
if (convertSignedToAbsoluteValues)
{
color = Color.FromArgb(
channelColors[channelIndex].IsRed ? (int)Math.Abs((int)currentChannelSample) : 0,
channelColors[channelIndex].IsGreen ? (int)Math.Abs((int)currentChannelSample) : 0,
channelColors[channelIndex].IsBlue ? (int)Math.Abs((int)currentChannelSample) : 0);
}
else
{
color = Color.FromArgb(
channelColors[channelIndex].IsRed ? (int)Math.Abs(currentChannelSample + 127) : 0,
channelColors[channelIndex].IsGreen ? (int)Math.Abs(currentChannelSample + 127) : 0,
channelColors[channelIndex].IsBlue ? (int)Math.Abs(currentChannelSample + 127) : 0);
}
SetPixel(pPixel, color);
pPixel += 1;
pixelColumnIndex++;
if (pixelColumnIndex == width && wastedPixels > 0)
{ //skip the invisible bytes and enter the next row
pixelColumnIndex = 0;
IntPtr pointer = new IntPtr(pPixel);
int newAddress = pointer.ToInt32() + wastedPixels;
pPixel = (PixelData*)newAddress;
}
}
}
}
finally
{
bmp.UnlockBits(bitmapData);
}
return bmp;
}
private unsafe void SetPixel(PixelData* pPixel, Color newColor)
{
pPixel->Red = (byte)newColor.R;
pPixel->Green = (byte)newColor.G;
pPixel->Blue = (byte)newColor.B;
}
}
}
|
By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.
If a file you wish to view isn't highlighted, and is a text file (not binary), please
let us know and we'll add colourisation support for it.