Sie sind auf Seite 1von 26

Generar msica con PIC

Etiquetas: 16F876, C, CCS, cdigo, Microcontrolador, Msica, PIC, rutina, Sonido

Antes que nada debo aclarar que no estamos hablando de msica con la calidad un CD, ni
stereo, ni nada por el estilo, es mas, dudo que tenga alguna calidad, lo digo para no crear
falsas expectativas.
En esta ocasin vamos a interpretar alguna meloda utilizando la tcnica que vimos en
"Generar sonido con PIC" y slo lo haremos a modo didctico, ya que es muy til para
comprender como el microcontrolador administra los tiempos y como se utilizan los puertos.
Este ejercicio bien podra reemplazar al ya mtico parpadeo de un LED con PIC16F84A ya que
es, en teora, el mismo principio pero con el agregado del control de la frecuencia.
Ahora bien, aqu viene lo mas interesante, investigando un poco me enter de como es esto
de las notas al encontrar este artculo: Frecuencias de las notas musicales , en el que se
explica la frmula para obtener la frecuencia de cada nota musical. Una de las frmulas, la
mas sencilla para llevar a cabo en un programa, es esta:

Con esta frmula pude sacar las frecuencias para las octavas 0 a la 6, que son las que mejor
se ejecutan en el PIC, mas arriba o mas abajo ya es molesto o inaudible. Dichas frecuencias
estan en la siguiente tabla:

DO
DO#
RE
RE#
MI
FA
FA#
SOL
SOL#
LA
LA#
SI

0
32,70
34,64
36,70
38,89
41,20
43,65
46,24
48,99
51,91
55,00
58,27
61,73

1
65,40
69,29
73,41
77,78
82,40
87,30
92,49
97,99
103,82
110,00
116,54
123,47

2
130,81
138,59
146,83
155,56
164,81
174,61
184,99
195,99
207,65
220,00
233,08
246,94

3
261,62
277,18
293,66
311,12
329,62
349,22
369,99
391,99
415,30
440,00
466,16
493,88

4
523,25
554,36
587,32
622,25
659,25
698,45
739,98
783,99
830,60
880,00
932,32
987,76

5
1046,50
1108,73
1174,65
1244,50
1318,51
1396,91
1479,97
1567,98
1661,21
1760,00
1864,65
1975,53

6
2093,00
2217,46
2349,31
2489,01
2637,02
2793,82
2959,95
3135,96
3322,43
3520,00
3729,31
3951,06

Tabla 1. Frecuencia en Hz de cada nota musical.


En esta otra tabla estn representadas los microsegundos necesarios entre estado alto y bajo
para generar dichas frecuencias:

DO
DO#
RE
RE#
MI
FA
FA#

0
15289,02
14430,91
13620,97
12856,48
12134,90
11453,82
10810,97

1
7644,51
7215,45
6810,48
6428,24
6067,45
5726,91
5405,48

2
3822,25
3607,72
3405,24
3214,12
3033,72
2863,45
2702,74

3
1911,12
1803,86
1702,62
1607,06
1516,86
1431,72
1351,37

4
955,56
901,93
851,31
803,53
758,43
715,86
675,68

5
477,78
450,96
425,65
401,76
379,21
357,93
337,84

6
238,89
225,48
212,82
200,88
189,60
178,96
168,92

SOL
SOL#
LA
LA#
SI

10204,20
9631,48
9090,90
8580,67
8099,07

5102,10
4815,74
4545,45
4290,33
4049,53

2551,05
2407,87
2272,72
2145,16
2024,76

1275,52
1203,93
1136,36
1072,58
1012,38

637,76
601,96
568,18
536,29
506,19

318,88
300,98
284,09
268,14
253,09

159,44
150,49
142,04
134,07
126,54

Tabla 2. Microsegundos de pausa correspondiente a medio periodo de cada nota musical.


Observando la ltima tabla con atencin se ve que la nota de la octava siguiente es igual a la
octava actual dividido por 2, o con la equivalencia en CCS C una rotacin a la derecha.
De esto se deduce que teniendo un array con los valores de las diferentes notas se puede ir
rotando a la derecha tantas veces como octavas queramos subir para obtener la nota y octava
precisas.
En C, la funcin encargada de hacerlo se ve as:

#define Speaker PIN_B0 #define nDO 0 // DO #define nDO_ 1 // DO# #define nRE 2 //
RE #define nRE_ 3 // RE# #define nMI 4 // MI #define nFA 5 // FA #define nFA_ 6 //
FA# #define nSOL 7 // SOL #define nSOL_ 8 // SOL# #define nLA 9 // LA #define
nLA_ 10 // LA# #define nSI 11 // SI int16 FreqNota[12]={ // retardos entre estado alto //
y bajo para generar las notas 15289, // DO 14430, // DO# 13620, // RE 12856, // RE#
12134, // MI 11453, // FA 10810, // FA# 10204, // SOL 9631, // SOL# 9090, // LA
8580, // LA# 8099 // SI }; void Play(int nota, int octava, int16 duracion){ int16 fn; int16
mS_Transcurridos=0; // Contadores necesarios // para controlar la duracin int16
CiclosL=0; // Contandor de uS fn=FreqNota[nota]; // Define los retardos para generar //
la frecuencia de cada nota fn>>=(octava); // Adapta la frecuencia a la octava actual //
haciendo una rotacin // a la derecha por octava do{ output_high(Speaker); // Genera la
frecuancia delay_us(fn); // con los retardos mientras CiclosL+=(fn); // aumenta el
contador de // ciclos transcurridos output_low(Speaker); // en dos partes para repartir el
delay_us(fn); // trabajo entre estado alto y bajo. CiclosL+=(fn); // CiclosL+=25; //
Compensador. while(CiclosL>999){ // Se queda en el bucle mientras CiclosL // sea
menor a 1000 (1 mS) CiclosL-=1000; // Le resta 1000 a CiclosL mS_Transcurridos++; //
y le suma 1 a mS_Transcurridos. CiclosL+=25; // Compensador. } }while
(duracion>mS_Transcurridos); // Repite el bucle hasta que haya // pasado el tiempo
indicado. }
Bueno, sabiendo como ejecutar las notas musicales ahora es tiempo de interpretar una
meloda.
Como aclar antes de msica no tengo conocimientos, pero buscando alguna meloda a
interpretar en el PIC record que en BASIC (el antguo) haba una funcin llamada PLAY y que
interpretaba las notas musicales con el PC Speaker. Buscando en Google encontr un artculo
titulado DJ QBASIC
donde hay diez canciones conocidas. Ahora lo que resta es adaptar el
cdigo BASIC a CCS con la funcin PLAY para C que vimos en la entrada "Generar sonido con
PIC", y es cuando vemos otro pequeo inconveniente, las notas musicales en BASIC estn en el
sistema de notacin musical ingls y nosotros usamos el latino, en la WikiPedia encontr este
artculo donde hablan de eso y se muestra la equivalencia: Escala musical .
En base a eso hice este ejemplo que interpreta una meloda segn se pulse una tecla, si se
pulsa la tecla conectada a RB1 suena "Pop Corn", si se pulsa RB2 suena "Ecuador" y si se pulsa
RB3 suena "The lion sleep tonight".

/////////////////////////////////////////////////////////////////////// // // // PICMusic v.1.00 // // (c) 2010

Gerardo Ariel Ramrez. // // // /////////////////////////////////////////////////////////////////////// // // //


uControlador: PIC16F876A Lenguaje: CCS C // // Xtal:
4MHz // // // /////////////////////////////////////////////////////////////////////// #include <16f876a.h>
#use delay(clock=4000000) #use fast_io(all) #fuses HS #FUSES NOPUT #FUSES
NOBROWNOUT #define Speaker PIN_B0 #define nDO 0 // DO #define nDO_ 1 //
DO# #define nRE 2 // RE #define nRE_ 3 // RE# #define nMI 4 // MI #define nFA 5 //
FA #define nFA_ 6 // FA# #define nSOL 7 // SOL #define nSOL_ 8 // SOL# #define
nLA 9 // LA #define nLA_ 10 // LA# #define nSI 11 // SI int16 FreqNota[12]={ //
retardos entre estado alto // y bajo para generar las notas 15289, // DO 14430, // DO#
13620, // RE 12856, // RE# 12134, // MI 11453, // FA 10810, // FA# 10204, // SOL 9631,
// SOL# 9090, // LA 8580, // LA# 8099 // SI }; void Play(int nota,int octava,int16
duracion); void PlayCancion(int cancion); void main(){ set_tris_b(14); // B<3:1>:
Pulsadores B0: Speaker while (true){ if(input(PIN_B1))PlayCancion(1); //Si pulso
switch 1 toca // Pop Corn if(input(PIN_B2))PlayCancion(2); //Si pulso switch 2 toca //
Ecuador if(input(PIN_B3))PlayCancion(3); //Si pulso switch 3 toca // The lion sleep
tonight } } void Play(int nota, int octava, int16 duracion){ int16 fn; int16
mS_Transcurridos=0; int16 CiclosL=0; fn=FreqNota[nota]; // Define los retardos para
generar // la frecuencia de cada nota fn>>=(octava); // Adapta la frecuencia // a la octava
actual do{ output_high(Speaker); // Genera la frecuancia delay_us(fn); // con los
retardos mientras CiclosL+=(fn); // aumenta el contador de // ciclos transcurridos
output_low(Speaker); // en dos partes para repartir el delay_us(fn); // trabajo entre estado
alto y bajo. CiclosL+=(fn); // CiclosL+=25; // Compensador. while(CiclosL>999){ // Se
queda en el bucle mientras // CiclosL sea menor a 1000 (1 mS) CiclosL-=1000; // Le
resta 1000 a CiclosL mS_Transcurridos++; // y le suma 1 a mS_Transcurridos.
CiclosL+=25; // Compensador. } }while (duracion>mS_Transcurridos); // Repite el
bucle hasta // que haya pasado el // tiempo indicado. } void PlayCancion(int cancion)
{ switch (cancion){ case 1: //POP CORN play (nDO ,5,166); play (nLA_ ,4,166); play
(nDO ,5,166); play (nSOL ,4,166); play (nRE_ ,4,166); play (nSOL ,4,166); play (nDO ,
4,166); delay_ms (166); play (nDO ,5,166); play (nLA_ ,4,166); play (nDO ,5,166); play
(nSOL ,4,166); play (nRE_ ,4,166); play (nSOL ,4,166); play (nDO ,4,166); delay_ms
(166); play (nDO ,5,166); play (nRE ,5,166); play (nRE_ ,5,166); play (nRE ,5,166);
play (nRE_ ,5,166); play (nDO ,5,166); play (nRE ,5,166); play (nDO ,5,166); play
(nRE ,5,166); play (nLA_ ,4,166); play (nDO ,5,166); play (nLA_ ,4,166); play (nDO ,
5,166); play (nSOL_ ,4,166); play (nDO ,5,166); break; case 2: //ECUADOR play
(nLA ,3,100); delay_ms (200); play (nMI ,3,100); delay_ms (200); play (nDO ,4,100);
delay_ms (100); play (nSI ,3,100); delay_ms (100); play (nRE ,4,100); delay_ms (100);
play (nSI ,3,100); delay_ms (100); play (nSOL ,3,100); delay_ms (100); play (nLA ,
3,100); delay_ms (200); play (nMI ,3,100); delay_ms (200); play (nDO ,4,100);
delay_ms (100); play (nSI ,3,100); delay_ms (100); play (nRE ,4,100); delay_ms (100);
play (nSI ,3,100); delay_ms (100); play (nSOL ,3,100); delay_ms (100); play (nDO ,
4,100); delay_ms (200); play (nSOL ,3,100); delay_ms (200); play (nMI ,4,100);
delay_ms (100); play (nRE ,4,100); delay_ms (100); play (nMI ,4,100); delay_ms (100);
play (nRE ,4,100); delay_ms (100); play (nSOL ,3,100); delay_ms (100); play (nDO ,
4,100); delay_ms (200); play (nLA ,3,100); delay_ms (200); play (nDO ,4,100);
delay_ms (100); play (nSI ,3,100); delay_ms (100); play (nDO ,4,100); delay_ms (100);
play (nSI ,3,100); delay_ms (100); play (nSOL ,3,100); break; case 3: //The lion sleep
tonight play (nDO ,3,125); delay_ms (250); play (nRE ,3,125); delay_ms (125); play
(nMI ,3,125); delay_ms (250); play (nRE ,3,125); delay_ms (250); play (nMI ,3,125);
play (nFA ,3,125); delay_ms (250); play (nMI ,3,125); delay_ms (125); play (nRE ,
3,125); delay_ms (250); play (nDO ,3,125); delay_ms (250); play (nRE ,3,125); play

(nMI ,3,125); delay_ms (250); play (nRE ,3,125); delay_ms (125); play (nDO ,3,125);
delay_ms (250); delay_ms (125); play (nMI ,3,125); delay_ms (125); play (nRE ,3,500);
break; } }
Si no lo quieres copiar, lo quieres modificar o lo quieres ya compilado, te puedes bajar el
proyecto completo haciendo click en este link .

http://picrobot.blogspot.com/2010/03/picmusic.html
generar sonido matlab
The duration for which a given vector will play depends on the number of elements in the
vector and the sampling rate. For example, a 1000-element vector, when played at 1 kHz,
will last 1 second. When played at 500 Hz, it will last 2 seconds. Therefore, the first choice
you should make is the sampling rate you want to use. To avoid aliasing, the sampling rate
should be twice as large as the largest frequency component of the signal. However, you
may want to make it even larger than that to avoid attenuation of frequencies close to the
sampling rate.
Given a sampling rate of 1 kHz, the following example creates a sound vector of a given
duration and tone frequency (using the LINSPACE and SIN functions):
Fs = 1000;
%# Samples per second
toneFreq = 50; %# Tone frequency, in Hertz
nSeconds = 2; %# Duration of the sound
y = sin(linspace(0, nSeconds*toneFreq*2*pi, round(nSeconds*Fs)));
When played at 1 kHz using the SOUND function, this vector will generate a 50 Hz tone for
2 seconds:
sound(y, Fs); %# Play sound at sampling rate Fs
The vector can then be saved as a wav file using the WAVWRITE function:
wavwrite(y, Fs, 8, 'tone_50Hz.wav'); %# Save as an 8-bit, 1 kHz signal
The sound vector can later be loaded using the WAVREAD function. If you're going to
concatenate two sound vectors, you should make sure that they are both designed to use
the same sampling rate.

Here's a function that wraps up the tone generation functionality.


function pureTone ( frequency, duration, amplitude, sampleFreq, save2file )
% Generate pure tones.
% Enter at least 1 argument.
% Defaults are:
% duration 1 sec
% amplitude 1
% sampleFreq 48000 Hz
% save2file no
%-------------------% If you want to save the tone to a file, provide a name.
switch nargin
case 0
error('Enter a frequency.')
case 1
duration = 1;

amplitude = 1;
sampleFreq = 48000;
save2file = 0;
case 2
amplitude = 1;
sampleFreq = 48000;
save2file = 0;
case 3
sampleFreq = 48000;
save2file = 0;
case 4
save2file = 0;
end

t = linspace( 0, duration, duration * sampleFreq );


% http://de.wikipedia.org/wiki/Sinuston
s = amplitude * sin( 2 * pi * frequency * t );
sound( s, sampleFreq );
if save2file
wavwrite( s, sampleFreq, 32, save2file);
end
end
http://stackoverflow.com/questions/1452455/how-do-you-generate-dualtone-frequencies-in-matlab

Generar ondas de sonido con c#


For the longest time, I've been baffled by the concept of sound in computing. How in the world is
sound store? How is it played back? In classic Coding4Fun style, we'll learn by doing in this
articleby building a wave oscillator application.

Optional Reading

I cover the basics of this article in a multi-part blog series, which you should check out if you
have trouble:
Part 1 - How Audio Data is Represented
Part 2 - Demystifying the WAV Format
Part 3 - Synthesizing Simple WAV Audio Using C#
Part 4 - Algorithms for Different Sound Waves in C#

What's An Oscillator?

An oscillator is a device or application that generates a waveform. In electrical engineering


terms, it's a device that outputs an electrical current with varying voltage. If you plot the voltage
over time, you get a regular wave in a particular form, such as a sine, square, triangle or
sawtooth.
An oscillator is the most basic type of synthesizer. Analog synths use electrical circuits to output
a sound wave. Digital synthesizers do the same thing, but with software.

You can create a pretty neat sounding instrument by combining the outputs of multiple
oscillators. For example, if you have three oscillators oscillating at a frequency of 440Hz
(concert A pitch), but each of them has a different waveform (saw, square, sine) you get a very
interesting, layered sound.
But before we get too deep into this subject, let's briefly explore the physics of sound.

The Physics of Sound

Sound happens when air pressure changes on your ear drum. When you clap in an empty
room, pressure waves bounce all over the place and dance on your eardrum. The changes in
pressure are detected continuously by your ear.
Digitally, pressure is referred to by a scalar value called amplitude. The amplitude (loudness)
of the wave is measured thousands of times per second (44,100 times per second on CDs).
Every measurement of pressure (aka amplitude) is called a sampleCDs are recorded with
44,100 samples per second, each with a value between the minimum and maximum amplitude
for the bit depth.
Think about 44,100 samples per second. That's a lot of stuff for your ear to detect. That's how
we're able to hear so much stuff going on in the mix of a song, especially in stereo tracks where
you have 44,100 samples per second, per ear.
It turns out that there is a horribly intense mathematical theorem which basically tells us that
44,100 samples per second is enough to accurately represent a pitch as high as 22 KHz. The
human ear can really hear only up to 20KHz, so a 44.1KHz sampling rate is a more than highenough sampling rate.
This whole section is expanded in detail on my blog:
Part 1 - How Audio Data is Represented

Terminology

So now you have a rather glancing overview of how sound works, and perhaps some clues as
to how we should go about representing it in computers. Let's go over all this new terminology
(plus some even newer terms) in delicious, bulleted format:
Sample: A measurement of a sound wave at a very small point in time. 44,100 of these
measurements in a row form a single channel of CD-quality audio.
Amplitude: The value of a sample. Max and min values are dependent upon the bit depth.
Bit depth: The number of bits used to represent a sample. 16-bit, 32-bit, etc. Max amplitude is
(2^depth) / 2 1.
Sample rate (aka sampling rate, aka bit rate): The number of samples per second of audio.
44,100 is standard for CD-quality audio.

How sound is represented

By now, you've probably surmised that a second of audio data is somehow represented by an
array of some integer data type, which has a length of 44,100. You would be correct in that
assumption. However, if you want sound to play from a computer's sound card, that data has to

be accompanied with a bunch of format information. WAV is probably the easiest format to deal
with.
See more in the following article:
Part 2 - Demystifying the WAV Format
You can also see how to build out a WAV file, old school and binary style, in the 3 rd part of that
series:
Part 3 - Synthesizing Simple WAV Audio Using C#
However, we are taking a slightly easier route, by using DirectSound. DirectSound gives us a lot
of nice classes for all the format information, abstracting all that stuff away and allowing us to
pump a stream of data into a DirectSound object and play it. Perfect for a synthesizer app!
So, let's get started!

Building the app

I learned some Blend while working with this app, since it's built on WPF. The image buttons are
just radio buttons. I had to differentiate the group number per instance of the user control at
runtime (in the constructor of the Oscillator class).

I'm a terrible UI designer for the most part, so this is about as sexy as I'm willing to make this
application. But feel free to make it look and act better!

Designing the UI
There's a dirty little secret in this application. It says it can oscillate 3 waves, but in truth, there's
a constant (set to 3) that you can modify. You could have six if you wanted. How did I
accomplish this? Each synth that you see is an instance of a WPF user control called
Oscillator.xaml:

I have a StackPanel called Oscs in the main window. In the Window_Loaded event handler of
the main window, I use this bit of code to add instances of the usercontrol:
C#
// Add 3 oscillators
Oscillator tmp;
for (int i = 0; i < NUM_GENERATORS; i++)
{
tmp = new Oscillator();
Oscs.Children.Add(tmp);
mixer.Oscillators.Add(tmp);
}
VB
' Add 3 oscillators
Dim tmp As Oscillator
Dim i As Integer = 0
While i < NUM_GENERATORS
tmp = New Oscillator()
Oscs.Children.Add(tmp)
mixer.Oscillators.Add(tmp)
System.Math.Max(System.Threading.Interlocked.Increment(i),i - 1)
End While
The long rectangular canvas is used to plot the values of the generated wave, so you can
visualize the wave as it's played. It is scaled along the X axis so you can see the general shape
of the wave, which would be impossible without scaling it with 44,100 samples per second.

Earlier in the article, I noted that a sound file is basically a really, really long array of 16- or 32-bit
floating point numbers between -1 and 1. We use this data to plot the graph as well. More on
that later.
Now that we have the UI figured out (dynamic addition of oscillators), let's take a look at exactly
how the sound is produced.

Bzzzzt! Making Sounds and the Mixer

One of the many cool things about DirectSound is that it basically wraps the WAV format for
you. You set the buffering/format options and then shove a bunch of data into it, and it will play.
Magic.
The way I've architected the solution is a little more modular. None of the oscillators has the
ability to play itselfrather, uses its UI to control some values such as frequency, amplitude and
wave type. These values are tied to public properties. The Oscillator component does virtually
no audio work at all.
The generation of audio data is handled by the custom Mixer class, which takes a collection of
Oscillators and, based on their properties, creates a composite of all the generators. This is
done by averaging the samples in every oscillator and putting them into a new array of data.

The Mixer class looks like this:

One of the workhorses of the Mixer class is the method GenerateOscillatorSampleData. This
takes an Oscillator as an argument to give access to the public properties set in the UI. From
there, the algorithm generates 1 second of sample data (specified by the
member bufferDurationSeconds) based on the wave type that has been selected in the UI.
This is where the mathy stuff comes in to play. Check out this method and the different cases in
the switch statement that determine what kind of wave to create below.
C#
public short[] GenerateOscillatorSampleData(Oscillator osc)
{
// Creates a looping buffer based on the params given
// Fill the buffer with whatever waveform at the specified frequency
int numSamples = Convert.ToInt32(bufferDurationSeconds *
waveFormat.SamplesPerSecond);
short[] sampleData = new short[numSamples];
double frequency = osc.Frequency;

int amplitude = osc.Amplitude;


double angle = (Math.PI * 2 * frequency) /
(waveFormat.SamplesPerSecond * waveFormat.Channels);
switch (osc.WaveType)
{
case WaveType.Sine:
{
for (int i = 0; i < numSamples; i++)
// Generate a sine wave in both channels.
sampleData[i] = Convert.ToInt16(amplitude *
Math.Sin(angle * i));
}
break;
case WaveType.Square:
{
for (int i = 0; i < numSamples; i++)
{
// Generate a square wave in both channels.
if (Math.Sin(angle * i) > 0)
sampleData[i] = Convert.ToInt16(amplitude);
else
sampleData[i] = Convert.ToInt16(-amplitude);
}
}
break;
case WaveType.Sawtooth:
{
int samplesPerPeriod = Convert.ToInt32(
waveFormat.SamplesPerSecond /
(frequency / waveFormat.Channels));
short sampleStep = Convert.ToInt16(
(amplitude * 2) / samplesPerPeriod);
short tempSample = 0;
int i = 0;
int totalSamplesWritten = 0;
while (totalSamplesWritten < numSamples)
{
tempSample = (short)-amplitude;
for (i = 0; i < samplesPerPeriod &&
totalSamplesWritten < numSamples; i++)
{
tempSample += sampleStep;
sampleData[totalSamplesWritten] = tempSample;
}

totalSamplesWritten++;

}
}
break;
case WaveType.Noise:
{
Random rnd = new Random();
for (int i = 0; i < numSamples; i++)
{
sampleData[i] = Convert.ToInt16(
rnd.Next(-amplitude, amplitude));
}
}

break;
}
return sampleData;

VB.Net
Public Function GenerateOscillatorSampleData(ByVal osc As Oscillator) As Short()
' Creates a looping buffer based on the params given
' Fill the buffer with whatever waveform at the specified frequency
Dim numSamples As Integer = Convert.ToInt32(
bufferDurationSeconds * waveFormat.SamplesPerSecond)
Dim sampleData As Short() = New Short(numSamples - 1) {}
Dim frequency As Double = osc.Frequency
Dim amplitude As Integer = osc.Amplitude
Dim angle As Double = (Math.PI * 2 * frequency) /
(waveFormat.SamplesPerSecond * waveFormat.Channels)
Select Case osc.WaveType
Case WaveType.Sine
If True Then
For i As Integer = 0 To numSamples - 1
' Generate a sine wave in both channels.
sampleData(i) =
Convert.ToInt16(amplitude * Math.Sin(angle * i))
Next
End If
Exit Select
Case WaveType.Square
If True Then
For i As Integer = 0 To numSamples - 1
' Generate a square wave in both channels.
If Math.Sin(angle * i) > 0 Then
sampleData(i) = Convert.ToInt16(amplitude)
Else
sampleData(i) = Convert.ToInt16(-amplitude)
End If
Next
End If
Exit Select
Case WaveType.Sawtooth
If True Then
Dim samplesPerPeriod As Integer =
Convert.ToInt32(waveFormat.SamplesPerSecond /
(frequency / waveFormat.Channels))
Dim sampleStep As Short =
Convert.ToInt16((amplitude * 2) / samplesPerPeriod)
Dim tempSample As Short = 0
Dim i As Integer = 0
Dim totalSamplesWritten As Integer = 0
While totalSamplesWritten < numSamples
tempSample = CShort(-amplitude)
i=0
While i < samplesPerPeriod AndAlso totalSamplesWritten <
numSamples
tempSample += sampleStep
sampleData(totalSamplesWritten) = tempSample

totalSamplesWritten += 1
i += 1
End While
End While
End If
Exit Select
Case WaveType.Noise
If True Then
Dim rnd As New Random()
For i As Integer = 0 To numSamples - 1
sampleData(i) = Convert.ToInt16(
rnd.[Next](-amplitude, amplitude))
Next
End If
Exit Select
End Select
Return sampleData
End Function
The Mixer is the heart of the app, and it's a beautiful example of object orientation and
cohesion. Give it three things (oscillators) and it spits out a new thing you can use (an array of
sample data).
Now that we have the sample data, all we have to do is play it back using DirectSound.

Sound Playback with DirectSound

As I mentioned, DirectSound provides a wrapper over the WAV format. You set up your buffer
and format information and then feed it a bunch of data in the form of an array of shorts (arrays
of trousers are known to cause errors).
First, we initialize the format information and buffer in the Window_Loaded event handler of the
main form. The values below are not really arbitrary; there is an explanation of them in the
Optional Reading section above (see Demystifying the WAV Format). This code also contains
the code to add the oscillators, as shown earlier in the article.
C#
private void Window_Loaded(object sender, System.Windows.RoutedEventArgs e)
{
WindowInteropHelper helper =
new WindowInteropHelper(Application.Current.MainWindow);
device.SetCooperativeLevel(helper.Handle, CooperativeLevel.Normal);
waveFormat = new Microsoft.DirectX.DirectSound.WaveFormat();
waveFormat.SamplesPerSecond = 44100;
waveFormat.Channels = 2;
waveFormat.FormatTag = WaveFormatTag.Pcm;
waveFormat.BitsPerSample = 16;
waveFormat.BlockAlign = 4;
waveFormat.AverageBytesPerSecond = 176400;
bufferDesc = new BufferDescription(waveFormat);
bufferDesc.DeferLocation = true;
bufferDesc.BufferBytes = Convert.ToInt32(
bufferDurationSeconds * waveFormat.AverageBytesPerSecond /
waveFormat.Channels);

// Add 3 oscillators
Oscillator tmp;
for (int i = 0; i < NUM_GENERATORS; i++)
{
tmp = new Oscillator();
Oscs.Children.Add(tmp);
mixer.Oscillators.Add(tmp);
}

VB
Private Sub Window_Loaded(ByVal sender As Object, ByVal e As
System.Windows.RoutedEventArgs)
Dim helper As New WindowInteropHelper(Application.Current.MainWindow)
device.SetCooperativeLevel(helper.Handle, CooperativeLevel.Normal)
waveFormat = New Microsoft.DirectX.DirectSound.WaveFormat()
waveFormat.SamplesPerSecond = 44100
waveFormat.Channels = 2
waveFormat.FormatTag = WaveFormatTag.Pcm
waveFormat.BitsPerSample = 16
waveFormat.BlockAlign = 4
waveFormat.AverageBytesPerSecond = 176400
bufferDesc = New BufferDescription(waveFormat)
bufferDesc.DeferLocation = True
bufferDesc.BufferBytes = Convert.ToInt32(
bufferDurationSeconds * waveFormat.AverageBytesPerSecond /
waveFormat.Channels)
' Add 3 oscillators
Dim tmp As Oscillator
For i As Integer = 0 To NUM_GENERATORS - 1
tmp = New Oscillator()
Oscs.Children.Add(tmp)
mixer.Oscillators.Add(tmp)
Next
End Sub
When you click the Play button, the application takes its collection of oscillators and passes the
values of the UI controls to the Mixer (which is initialized on each click with a reference to the
main form window, so it can grab the Oscillator user controls).
The mixer outputs an array of shorts, which we write to a DirectSound buffer.
Here is the code for the Play button's click event handler:
C#
private void btnPlay_Click(object sender, System.Windows.RoutedEventArgs e)
{
mixer.Initialize(Application.Current.MainWindow);
short[] sampleData = mixer.MixToStream();
buffer = new SecondaryBuffer(bufferDesc, device);

buffer.Write(0, sampleData, LockFlag.EntireBuffer);


buffer.Play(0, BufferPlayFlags.Default);
GraphWaveform(sampleData);
}
VB
Private Sub btnPlay_Click(sender As Object, e As
System.Windows.RoutedEventArgs)
mixer.Initialize(Application.Current.MainWindow)
Dim sampleData As Short() = mixer.MixToStream()
buffer = New SecondaryBuffer(bufferDesc, device)
buffer.Write(0, sampleData, LockFlag.EntireBuffer)
buffer.Play(0, BufferPlayFlags.[Default])
GraphWaveform(sampleData)
End Sub

Drawing Pretty Graphs

All that's left is to draw the graph of the waveform on the canvas. Below is the GraphWaveform
method. This method could graph anything it wanted to, as long as it was an array of shorts (not
trousers). It's reminiscent of trying to graph things using Flash back in the day, when you had to
actually figure out points and lines (most likely on paper), but WPF's Polyline object makes this
rather trivial.
C#
private void GraphWaveform(short[] data)
{
cvDrawingArea.Children.Clear();
double canvasHeight = cvDrawingArea.Height;
double canvasWidth = cvDrawingArea.Width;
int observablePoints = 1800;
double xScale = canvasWidth / observablePoints;
double yScale = (canvasHeight /
(double)(amplitude * 2)) * ((double)amplitude / MAX_AMPLITUDE);
Polyline graphLine = new Polyline();
graphLine.Stroke = Brushes.Black;
graphLine.StrokeThickness = 1;
for (int i = 0; i < observablePoints; i++)
{
graphLine.Points.Add(
new Point(i * xScale, (canvasHeight / 2) - (data[i] * yScale) ));
}
}

cvDrawingArea.Children.Add(graphLine);

VB

Private Sub GraphWaveform(ByVal data As Short())


cvDrawingArea.Children.Clear()
Dim canvasHeight As Double = cvDrawingArea.Height
Dim canvasWidth As Double = cvDrawingArea.Width
Dim observablePoints As Integer = 1800
Dim xScale As Double = canvasWidth / observablePoints
Dim yScale As Double = (canvasHeight / CDbl((amplitude * 2))) *
(CDbl(amplitude) / MAX_AMPLITUDE)
Dim graphLine As New Polyline()
graphLine.Stroke = Brushes.Black
graphLine.StrokeThickness = 1
For i As Integer = 0 To observablePoints - 1
graphLine.Points.Add(
New Point(i * xScale, (canvasHeight / 2) - (data(i) * yScale)))
Next
cvDrawingArea.Children.Add(graphLine)
End Sub

Conclusion

This was a really fun little project that took way less time to code than it does to explain. It's a
great exercise because it requires you to think about an ancillary field of science before you can
sit down and code, which is really what coding for fun's all about, anyway!
If you want to try this out, the download link for the source code is at the top of the article.

http://channel9.msdn.com/coding4fun/articles/Generating-Sound-Waveswith-C-Wave-Oscillators

Generate Ring Tones on your PIC16F87x


Microcontroller
Using only a speaker and decoupling capacitor, it is possible to generate tunes
or melodies from your Microchip PIC16F87x processor. A timer can be used to
generate each of the eleven musical notes and another timer can be used to
time the note duration. You can even choose to support several octaves if you
want a challenge.
The code can form the foundation for a range of applications such as christmas
toys to customised doorbells or chimes. Add a DIP switch to support multiple
melodies.
However the hard parts comes from making and coding your own melodies to
play. Wouldnt it make sense to use some of the tens of thousands of Mobile
Phone Ring Tones floating around the place. This is what we have done here.
One of the more popular standards is the RTTTL (Ringing Tones Text Transfer

Language) specification which is used by Nokia mobile phones. These tunes


can be save and transported using the .RTX ringtone specification. This
specification is no more than a ASCII text file which includes the ringtone name,
a control section specifying default attributes and a comma delimited string of
notes that can be optionally encoded with the octave and duration.
Understanding RTTTL (Ringing Tones Text Transfer Language)
A simple RTTTL ring tone is the itchy and scratchy theme song which is
displayed below :
itchy:d=8,o=6,b=160:c,a5,4p,c,a,4p,c,a5,c,a5,c,a,4p,p,c,d,e,p,e,f,g,4p
,d,c,4d,f,4a#,4a,2c7

This ring tone can be split into three sections :

Title : The title of the ring tone starts the string followed by a semicolon.
There are varying specifications on its maximum length but it is
suggested it shouldnt be any more than 10 characters long.

Control : The control section sets up default parameters which are


carried throughout the melody. The idea is to reduce the size of the
string, by omitting common parameters. Instead of each comma
delimited note containing the note, octave and duration information, the
octave and duration can be omitted if it is the same than the specified
default.

Note Commands :The body of the ring tone is made up of comma


delimited notes prefixed by the optional duration and postfixed by the
octave. A dotted note (.) can be specified after the octave which indicates
the duration of the note is extended by 50%, making it 1.5x the original
note duration.

The parameters which can be specified in the control section are :

d (default duration). The default duration can be one of 1, 2, 4, 8, 16, 32


or 64. The default duration can be one of 1, 2, 4, 8, 16, 32 or 64. 1
specifies a Semibreve (Whole Note), 2 indicates it a Minim (Half Note), 4
is a Crotchet (Quarter Note) etc up to 64 which is a
Hemidemisemiquaver (64th note). .

o (default octave). The default octave (scale) can be 4, 5, 6, or 7.

b (beats per minute). The BPM or tempo can be any one of the following
values 25, 28, 31, 35, 40, 45, 50, 56, 63, 70, 80, 90, 100, 112, 125, 140,
160, 180, 200, 225, 250, 285, 320, 355, 400, 450, 500, 565, 635, 715,
800, 900.

s (style). Styles can be S=Staccato, N=Natural, C=Continuous.

l (looping). The loop value can be 0 to 15. 0 disables looping. 15 enables


infinite looping. Values between 1 and 14 specify how many loops to
make before stopping.

If any of the parameters is missing from the control section, the following
defaults are assumed : 4=duration, 6=scale, 63=beats-per-minute.
The circuit
As you can see the circuit required to generate tones is very simple. The
20MHz crystal controls the timing and can not be substituted with another value
without recalculating the divisors for each tone.

Calculations for 20MHz


The following spreedsheet shows the desired and actual frequencies for each
note @ 20MHz. The code supports 4 octaves.

The source code


The code has been written in C and compiled with the Hi-Tech PICC Compiler.
HiTech Software have a demo version of the PICC for download which works
for 30 days. A pre-compiled .HEX file has be included in the archive which has
been compiled for use with (or without) the ICD.
To add new tones is simply a matter of cut and paste. You may choose to
search Overtonez.co.uk or any number of internet sites for new ring tones.
Once you have one, simply cut the note commands into the Melody[ ] array
and adjust the defaultduration, defaultoctave and beat_speed to suit.
If you have a Nokia F-Bus cable, you can download your favourite tunes from
your phone using software such as Logomanager or the Oxygen Phone
Manager. You can then save them as .rtl files and paste it into your source
code.
/
**********************************************************************
*******/
/*
*/
/*
RTTTL Ring Tone Player for Microchip PIC16F87x
Microcontrollers
*/
/*
Copyright Craig.Peacock@beyondlogic.org
*/
/*
Version 1.0 17th August 2003
*/
/*
*/
/
**********************************************************************
*******/
#include <pic.h>
#define TONE

RB0

void InitTimer(void);
void delayms(unsigned char cnt);
void PlayNote(unsigned short note, unsigned char octave, unsigned int
duration);
unsigned
unsigned
unsigned
unsigned
unsigned

char beep;
char preloadTMR1L;
char preloadTMR1H;
short TMR0Count;
char beat_speed;

#define MissionImpossible
void main(void)
{
unsigned int pointer = 0;
unsigned int octave = 0;
unsigned int duration = 0;
unsigned short note = 0;

unsigned int defaultoctave = 0;


unsigned int defaultduration = 0;
#ifdef AxelF
/* AxelF */
const unsigned char static Melody[] =
{"32p,8g,8p,16a#.,8p,16g,16p,16g,8c6,8g,8f,8g,8p,16d.6,8p,16g,16p,
16g,8d#6,8d6,8a#,8g,8d6,8g6,16g,16f,16p,16f,8d,8a#,2g,4p,16f6,8d6,
8c6,8a#,4g,8a#.,16g,16p,16g,8c6,8g,8f,4g,8d.6,16g,16p,16g,8d#6,86,
8a#,8g,8d6,8g6,16g,16f,16p,16f,8d,8a#,2g"};
defaultoctave = 5;
defaultduration = 4;
beat_speed = 125;
#endif
#ifdef HappyBirthday
/* HappyBirthday */
const unsigned char static Melody[] =
{"8g.,16g,a,g,c6,2b,8g.,16g,a,g,d6,2c6,8g.,16g,g6,e6,c6,b,a,8f6.,16f6,
e6,c6,d6,2c6,8g.,16g,a,g,c6,2b,8g.,16g,a,g,d6,2c6,8g.,16g,g6,e6,c6,b,
a,8f6.,16f6,e6,c6,d6,2c6"};
defaultoctave = 5;
defaultduration = 4;
beat_speed = 125;
#endif
#ifdef Itchy
/* Itchy & Scratcy */
const unsigned char static Melody[] =
{"8c,8a5,4p,8c,8a,4p,8c,a5,8c,a5,8c,8a,4p,8p,8c,8d,8e,8p,8e,8f,8g,4p,8
d,
8c,4d,8f,4a#,4a,2c7"};
defaultoctave = 6;
defaultduration = 8;
beat_speed = 198;
#endif
#ifdef MissionImpossible
/* Mission Impossible */
const unsigned char static Melody[] =
{"16d5,16d#5,16d5,16d#5,16d5,16d#5,16d5,16d5,16d#5,16e5,16f5,16f#5,16g
5,
8g5,4p,8g5,4p,8a#5,8p,8c6,8p,8g5,4p,8g5,4p,8f5,8p,8p,8g5,4p,4p,8a#5,8p
,
8c6,8p,8g5,4p,4p,8f5,8p,8f#5,8p,8a#5,8g5,1d5"};
defaultoctave = 6;
defaultduration = 4;
beat_speed = 150;
#endif
TRISB0 = 0;
beep = 0;

/* Make TONE an output */

InitTimer();
PEIE = 1;
GIE = 1;

/* Enable General Purpose Interrupts */

do {
octave = defaultoctave;

/* Set Default Octave */

if ((Melody[pointer] == '3') && (Melody[pointer+1] == '2')) {


duration = 32;
pointer += 2;
}
else if ((Melody[pointer] == '1') && (Melody[pointer+1] ==
'6')) {

duration = 16;
pointer += 2;

}
else if (Melody[pointer] == '8') {
duration = 8;
pointer++;
}
else if (Melody[pointer] == '4') {
duration = 4;
pointer++;
}
else if (Melody[pointer] == '2') {
duration = 2;
pointer++;
}
else if (Melody[pointer] == '1') {
duration = 1;
pointer++;
} else duration = defaultduration;
if (Melody[pointer + 1] == '#') {
/* Process Sharps */
switch (Melody[pointer]) {
case 'a' : note = 10726;
break;
case 'c' : note = 9019;
break;
case 'd' : note = 8035;
break;
case 'f' : note = 6757;
break;
case 'g' : note = 6024;
break;
}
pointer +=2;
} else {
switch (Melody[pointer]) {
case 'a' : note = 11364;
break;
case 'b' : note = 10123;
break;
case 'c' : note = 9555;

break;
case 'd' : note =
break;
case 'e' : note =
break;
case 'f' : note =
break;
case 'g' : note =
break;
case 'p' : note =
break;

8513;
7584;
7158;
6378;
0;

}
pointer++;

if (Melody[pointer] == '.') {
/* Duration 1.5x */
duration = duration + 128;
pointer++;
}
if (Melody[pointer] == '4') {
octave = 4;
pointer++;
} else if (Melody[pointer] == '5') {
octave = 5;
pointer++;
} else
if (Melody[pointer] == '6') {
octave = 6;
pointer++;
} else
if (Melody[pointer] == '7') {
octave = 7;
pointer++;
}
if (Melody[pointer] == '.') {
/* Duration 1.5x */
duration = duration + 128;
pointer++;
}
PlayNote(note, octave, duration);

} while (Melody[pointer++] == ',');


/* Wait until last note has played */
while(TMR0Count) { };
beep = 0;
/* Loop */
while(1) {};
}
void PlayNote(unsigned short note, unsigned char octave, unsigned int
duration)
{
/* Process octave */
switch (octave) {

case 4 : /*
break;
case 5 : /*
note =
break;
case 6 : /*
note =
break;
case 7 : /*
note =
break;

Do noting */
%2 */
note >> 1;
%4 */
note >> 2;
%8 */
note >> 4;

/* Wait until last note has played */


while(TMR0Count) { };
beep = 0;
/* Process New Note Frequency */
if (note) {
note = ~note;
preloadTMR1L = (note & 0xFF);
preloadTMR1H = ((note & 0xFF00) >> 8);
}
/* Process Note Duration */
TMR0Count = 255/(duration & 0x7F);
/* If duration is 1.5x add .5 to duration */
if (duration & 0x80) TMR0Count = (TMR0Count + (TMR0Count >> 1));
if (note) beep = 1;
}
void InitTimer(void)
{
/* Initialise Timer 0
OPTION = 0b11010111;
T0IF = 0;
T0IE = 1;

*/
/* Set TMR0 to Internal CLk, 1:256 */
/* Clear TMR0 Flag, ready for use */
/* Enable Timer Overflow Interrupt */

/* Initialise Timer 1 */
T1CON = 0b00000001;
/* Counter Enabled, Using Ext Pin 1:1
Prescaler */
TMR1IF = 0;
/* Clear Flag */
TMR1IE = 1;
/* Enable Interrupt */
}
void interrupt interr(void)
{
if (T0IF) {
TMR0 = beat_speed;
if (TMR0Count) TMR0Count--;
T0IF = 0;
}
if (TMR1IF) {
if (beep) TONE = !TONE;
else
TONE = 0;
TMR1H = preloadTMR1H;
TMR1L = preloadTMR1L;
TMR1IF = 0; /* Clear Flag */
}

The above example compiled with the Mission Impossible theme takes a
modest 1K of memory. .
Memory Usage Map:
Program ROM
Program ROM
Program ROM

$0000 - $004D
$006F - $01BA
$05B9 - $07FF

$004E
$014C
$0247
$03E1

(
(
(
(

Bank 0 RAM
Bank 0 RAM

$0020 - $0038
$0071 - $0078

$0019 (
$0008 (
$0021 (

78)
332)
583)
993)

words
words
words
words total Program ROM

25) bytes
8) bytes
33) bytes total Bank 0 RAM

Program statistics:
Total ROM used
Total RAM used

993 words (12.1%)


33 bytes (9.0%)

Downloading the Source Code

Version 1.0, 14k bytes


Revision History
o 17th August 2003 - Version 1.0.

Glossary

Semibreve - Semibreve is the British term for Whole Note. A semibreve


is worth 4 beats.

Minim - Minim is the British term for Half Note. A minim is worth 2 beats.

Crotchet - Crotchet is the British term for Quarter Note. A crotchet is


worth 1 beat.

Quaver - Quaver is the British term for 8th Note. A quaver is worth 1/2 a
beat.

Semiquaver - Semiquaver is the British term for 16th Note. A


semiquaver is worth 1/4 of a beat.

Demisemiquaver - Demisemiquaver is the British term for 32nd Note. A


demisemiquaver is worth 1/8 of a beat.

Hemidemisemiquaver - Hemidemisemiquaver is the British term for


64th Note. A hemidemisemiquaver is worth 1/16 of a beat.

Octave - With the 12 musical notes, let's call note number one C. If we

start on C and work our way up the 12 notes in pitch, we will eventually
hit C again but of a higher pitch (exactly one octave higher). At this point
the C we are playing is in the next Octave on from the C we started on.
For example if we started on C4 (4th Octave) we would end up on C5
(5th Octave) and this can keep going endlessly until the frequency of the
pitch reaches beyond our aural hearing frequency range. The same can
apply going down in pitch / octaves. So Octave is specifiying what
Octave or Pitch/Frequency Range to play your specified note from.

Staccato - Staccato is a direction to perform a note quickly, lightly, and


seperated from the notes before and after it. Staccato performance in
practice reduces the time value of a note by 50%, thus a staccato'd
crotchet (quarter note) lasts only as long as a quaver (8th note).

Links

http://www.ringtone-converters.com/christmas - Many Christmas Ring


Tone Tunes in RTTTL Format.

http://overtonez.co.uk/frame_me/index.pl - OvertoneZ - Search and


download ASCII RTTTL Ring Tones to import into your source code.

http://www.htsoft.com/ HiTech Software - Make the PICC C Cross


Compiler for Microchip PIC16x Family of Microcontrollers.

http://www.microchip.com - Microchip PIC Series of Microcontrollers

http://www.microchipc.com - Program Microchip PIC micros with C

http://retired.beyondlogic.org/pic/ringtones.htm

Das könnte Ihnen auch gefallen