Table of Contents

OSA usage example: QUARTET

Project

Program quartet synthesizes melody by given notes (it plays part of J.S.Bach N1067). Unpack file quartet4.rar into foulder "C:\TEST\QUARTET". This project is written for PIC16F628A.

This program generates 4-channel melody, using 8-bit PWM at 78 KHz. Music is played with four samples: bass, violin and two guitars (of course, my samples are very different from reality). Each channel can be switched ON or OFF by switches.

Here is MP3 (download), recorded from device's output.

Scheme

The scheme of device is below. 8-bit digital signal from PWM through RC-filter R22-C3 and volume regulator R24 goes to headphones.

quartet_pwm.jpg

Switch SW1 switchs bass ON/OFF.
Switch SW2 switchs voilin ON/OFF.
Switch SW3 switchs first guitar ON/OFF.
Switch SW4 switchs second guitar ON/OFF.

Description

Program consists of four files:

quartet_main.c described below.

Program consists of 5 tasks. Four of them are "musicants", and fifth is "conductor" that sinchronize first four tasks.

Sound control

To control sound type TSound defined in quartet_main.c. Variables of this type contains all sound information for "musicant":

for "conductor":

for synthesiser:

"Musicants"

There are four "musicants" in program: Task_BASS, Task_VIOLIN, Task_GUITAR1, Task_GUITAR2. All time "musicants" wait for command from "conductor" (binary semaphore). When getting command, "musicant" reads next note from his notelist, plays it (task forms data for synthesizer).

Note produced in NoteWork() function, witch gets variable of sound control TSound in parameters. Using data of this variable function reads next note (or instruction: repeat, pause, ect.) and makes several operations to form parameters to synthesizer, in addition this function forms duration varianble. This variable decreases on every command from "conductor".

When "musicant" reaches the end of notelist, he informs "conductor" about it (task clears corresponding bit in flag variable flags_Playing). After that "musicant" begins to wait for "conductor's" command to restart music (binary semaphore BS_START). When "musicant" gets it, he goes to begin of notelist (sound control variable re-initializates) and sends "conductor's" command to next"musicant".

"Conductor"

Taks_CONDUCTOR ("conductor") has priority lower then "musicants". "Conductor" counts ticks like metronome and looks to ensure that all "musicants" get commands at correct time. Task_CONDUCTOR gets control every 150 ms. On every execution it checks for music state. When "conductor" determines that all "musicants" stopped to play (all bits in flag variable flag_Playing are cleared), he sends command to "musicants" to restart musig from the begin.

Two words about synthesizer

Sinthesizer is placed into interrupt routine if file interrupt.c. Interrupt takes all sound control variables and forms digital signal. To make this work it has:

All four current values of sound are summed up, and then result is divided by four. After that result copies into m_cDAC variable. On next start of interrupt routine this variable will be copied into PWM buffer register CCPR1L (two least significant bits are copied into bits CCP1X and CCP1Y of CCP1CON register).

About notelists

Few information about notelist will help you to create your music.

File music.c contains definitions for notes: C0, C0_, D0, D0_, ect. There are definitons for 57 frequencies (four with half octaves). When we create note in notelist (arrays notelist_xxx) using macro play(), we can to set note only with 5 bits (32 notes). Therefore there is possibleness to set base octave (macro setbase()). Note duration is selected using only 2 bits, so only four durations can be set. If you need note with longest duration, you can use either macro pause() (pauses sound by several ticks) or macro playmore() (continue playing sound for several ticks).

Notelist must be ended with macro stop() to avoid reading unitializes memory cells.