generar música con pic

Upload: paromarye1

Post on 10-Jan-2016

32 views

Category:

Documents


0 download

DESCRIPTION

Generar Música Con PIC

TRANSCRIPT

Generar msica con PICEtiquetas:16F876,C,CCS,cdigo,Microcontrolador,Msica,PIC,rutina,SonidoAntes 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:0123456

DO32,7065,40130,81261,62523,251046,502093,00

DO#34,6469,29138,59277,18554,361108,732217,46

RE36,7073,41146,83293,66587,321174,652349,31

RE#38,8977,78155,56311,12622,251244,502489,01

MI41,2082,40164,81329,62659,251318,512637,02

FA43,6587,30174,61349,22698,451396,912793,82

FA#46,2492,49184,99369,99739,981479,972959,95

SOL48,9997,99195,99391,99783,991567,983135,96

SOL#51,91103,82207,65415,30830,601661,213322,43

LA55,00110,00220,00440,00880,001760,003520,00

LA#58,27116,54233,08466,16932,321864,653729,31

SI61,73123,47246,94493,88987,761975,533951,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:0123456

DO15289,027644,513822,251911,12955,56477,78238,89

DO#14430,917215,453607,721803,86901,93450,96225,48

RE13620,976810,483405,241702,62851,31425,65212,82

RE#12856,486428,243214,121607,06803,53401,76200,88

MI12134,906067,453033,721516,86758,43379,21189,60

FA11453,825726,912863,451431,72715,86357,93178,96

FA#10810,975405,482702,741351,37675,68337,84168,92

SOL10204,205102,102551,051275,52637,76318,88159,44

SOL#9631,484815,742407,871203,93601,96300,98150,49

LA9090,904545,452272,721136,36568,18284,09142,04

LA#8580,674290,332145,161072,58536,29268,14134,07

SI8099,074049,532024,761012,38506,19253,09126,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 tituladoDJ QBASICdonde 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 #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: 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 estelink.http://picrobot.blogspot.com/2010/03/picmusic.htmlgenerar sonido matlabThe 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 avoidaliasing, the sampling rate should betwice 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 theLINSPACEandSINfunctions):Fs = 1000; %# Samples per secondtoneFreq = 50; %# Tone frequency, in HertznSeconds = 2; %# Duration of the soundy = sin(linspace(0, nSeconds*toneFreq*2*pi, round(nSeconds*Fs)));When played at 1 kHz using theSOUNDfunction, this vector will generate a 50 Hz tone for 2 seconds:sound(y, Fs); %# Play sound at sampling rate FsThe vector can then be saved as a wav file using theWAVWRITEfunction:wavwrite(y, Fs, 8, 'tone_50Hz.wav'); %# Save as an 8-bit, 1 kHz signalThe sound vector can later be loaded using theWAVREADfunction. 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/Sinustons = amplitude * sin( 2 * pi * frequency * t );

sound( s, sampleFreq );

if save2file wavwrite( s, sampleFreq, 32, save2file);end

endhttp://stackoverflow.com/questions/1452455/how-do-you-generate-dual-tone-frequencies-in-matlabGenerar 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 ReadingI 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 RepresentedPart 2 - Demystifying the WAV FormatPart 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 SoundSound 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 calledamplitude.The amplitude (loudness) of the wave is measured thousands of times per second (44,100 times per second on CDs). Every measurement of pressure (akaamplitude) is called asampleCDs are recorded with 44,100samplesper second, each with a value between the minimum and maximum amplitude for the bit depth.Think about 44,100 samples per second. That's alotof 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 ahorribly intense mathematical theoremwhich 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 high-enough sampling rate.This whole section is expanded in detail on my blog:Part 1 - How Audio Data is RepresentedTerminologySo 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 representedBy 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 FormatYou can also see how to build out a WAV file, old school and binary style, in the 3rdpart 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 appI 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 UIThere'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 oscillatorsOscillator tmp;for (int i = 0; i < NUM_GENERATORS; i++){ tmp = new Oscillator(); Oscs.Children.Add(tmp); mixer.Oscillators.Add(tmp);}VB' Add 3 oscillatorsDim tmp As OscillatorDim i As Integer = 0While 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 WhileThe 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 areally, really long arrayof 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 MixerOne 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.

TheMixerclass looks like this:

One of the workhorses of the Mixer class is the methodGenerateOscillatorSampleData. 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 memberbufferDurationSeconds) 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.NetPublic 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 sampleDataEnd FunctionThe 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 DirectSoundAs 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 (seeDemystifying 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); } }VBPrivate 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) NextEnd SubWhen 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);}VBPrivate 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 SubDrawing Pretty GraphsAll 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); }VBPrivate 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 SubConclusionThis 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-Waves-with-C-Wave-OscillatorsGenerate Ring Tones on your PIC16F87x MicrocontrollerUsing 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,2c7This 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 circuitAs 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 20MHzThe following spreedsheet shows the desired and actual frequencies for each note @ 20MHz. The code supports 4 octaves.

The source codeThe code has been written in C and compiled with theHi-Tech PICC Compiler. HiTech Software have ademo versionof 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 searchOvertonez.co.ukor any number of internet sites for new ring tones. Once you have one, simply cut the note commands into theMelody[ ]array and adjust thedefaultduration,defaultoctaveandbeat_speedto suit.If you have a Nokia F-Bus cable, you can download your favourite tunes from your phone using software such asLogomanageror theOxygen 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 [email protected] *//* Version 1.0 17th August 2003 *//* *//*****************************************************************************/

#include

#define TONE RB0

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

unsigned char beep;unsigned char preloadTMR1L;unsigned char preloadTMR1H;unsigned short TMR0Count;unsigned 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,8d, 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,16g5, 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; /* Make TONE an output */

beep = 0;

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 = 8513; break; case 'e' : note = 7584; break; case 'f' : note = 7158; break; case 'g' : note = 6378; break; case 'p' : note = 0; break; } 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 : /* Do noting */ break; case 5 : /* %2 */ note = note >> 1; break; case 6 : /* %4 */ note = note >> 2; break; case 7 : /* %8 */ note = note >> 4; break; }

/* 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; /* Set TMR0 to Internal CLk, 1:256 */ T0IF = 0; /* Clear TMR0 Flag, ready for use */ T0IE = 1; /* 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 $0000 - $004D $004E ( 78) wordsProgram ROM $006F - $01BA $014C ( 332) wordsProgram ROM $05B9 - $07FF $0247 ( 583) words $03E1 ( 993) words total Program ROM

Bank 0 RAM $0020 - $0038 $0019 ( 25) bytesBank 0 RAM $0071 - $0078 $0008 ( 8) bytes $0021 ( 33) bytes total Bank 0 RAM

Program statistics:

Total ROM used 993 words (12.1%)Total RAM used 33 bytes (9.0%)Downloading the Source Code Version 1.0, 14k bytesRevision History 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