ColecoVision

Development

Tutorial

Introduction to sdcc

ColecoVision system

Playing music

Hardware

Homebrew kits

Homebrew kit CV

Homebrew kit CVs

Software

libcv and libcvu

png2cv

png2cvs

abc2cvm

cvmtuning

compression utilities

Games

colecovision.eu

ColecoVision

How to buy

STM8

MCS-51

LLVM+SDCC

Contact

Playing music on the ColecoVision video game system

Introduction

While the ColecoVision has only very basic sound hardware it is nevertheless possible to make it play music using only a moderate amount of memory and CPU time. This tutorial shows how to do this using abc2cvm and libcvu.

Prerequisites

You should have installed a sdcc/libcv/libcvu based ColecoVision development system (e.g. as documented in tutorial 0). abc2cvm 0.8 or newer should be installed.

Playing music in 7 lines of code

This is main.c from the simplemusic example from the demo directory of libcv/libcvu:

#include "cv.h"
#include "cvu_sound.h"

extern const uint16_t notes[];

struct cvu_music music;

void play(void)
{
	cvu_play_music(&music);
}

void main(void)
{
	cvu_init_music(&music);
	music.notes = notes;
	cv_set_vint_handler(&play);
	cv_set_colors(CV_COLOR_BLACK, CV_COLOR_BLACK);
	cv_set_screen_active(true);
	for(;;);
}

To play the tune a structure of type cvu_music is used. cvu_init_music() initializes it to default values, which are good enough for this simple example, except for the notes, which are explicitly set to to array "notes". The screen is set to active to ensure that vertical retrace interrupts occur. At each vertical retrace interrupt the function cvu_play_music() is called, which plays the tune from the structure music.

The array "notes" has been generated from the file downbytheriverside.abc using abc2cvm:

X: 1
T: Down by the River Side
M: 3/4
L: 1/4
B: "O'Neill's 45"
N: "Slow" "collected by F. O'Neill"
Z: "Transcribed by Norbert Paap, norbertp@bdu.uva.nl"
F:http://trillian.mit.edu/~jc/music/book/oneills/1850/F/0001-0050_np.abc	 2007-08-26 08:28:26 UT
K:G
d/2c/2 | B G G | F G A | B G B | c2 B/2-c/2 |\
d g g | d e =f | A F A | c2 d/2c/2 |
B G G | F G A | B G B | c2 B/2c/2 |\
d g g | d e =f | A G G | G2 ||
B/2c/2 | d g f | g a g | =f d e | =fzd |\
g f d | c d c | d B G | F2 c/2B/2 |
B G G | F G A | B G B | c2 B/2c/2 |\
d g g | d e =f | A G G | G2 |:|

As you can see the abc music format is easy to understand. For further information about it consult the standard. There exist tools for generating abc files from midi files.

A more advanced example

There is another music demo that comes with libcv/libcvu. It lets the listener select among six of Mozart's horn duets.

This time the vertical retrace interrupt handler is a little longer than one line:

void play(void)
{
	if(change)
		return;
	cvu_play_music(&voice1);
	cvu_play_music(&voice2);
}

Two structures of type cvu_struct_music are used (one per voice / horn), both are played, unless the variable "change" is set to true (to avoid annoying noises at the moment the listener selects another duet).

The main loop looks like this:

for(;;)
{
	do
		cv_get_controller_state(&cs, 0);
	while(cs.keypad < 1 || cs.keypad > NUM_PIECES);

	change = true;
	cvu_init_music(&voice1);
	cvu_init_music(&voice2);
	voice1.channel = CV_SOUNDCHANNEL_0;
	voice2.channel = CV_SOUNDCHANNEL_1;
	voice1.sixteenth_notes_per_second = 5;
	voice2.sixteenth_notes_per_second = 5;
	voice1.notes = voices1[cs.keypad - 1];
	voice2.notes = voices2[cs.keypad - 1];
	change = false;
}

Once a duet has been selected the structures are initialized to default values. Since we have two voices two sound channels are needed. A speed of 5 sixteenth notes per second is selected.

The cvu_music struct

struct cvu_music
{
	enum cv_soundchannel channel;
	const uint8_t *volume;
	const uint16_t *tuning;
	uint8_t sixteenth_notes_per_second;
	const uint16_t *notes;
	
	uint16_t note_vint_remaining;
	uint16_t pause_vint_remaining;
};

This struct should be initialized using cvu_init_music(), which sets all it's members to sensible default values.

channel holds the channel that will be used to play the music. Valid values are CV_SOUNDCHANNEL_0, CV_SOUNDCHANNEL_1, CV_SOUNDCHANNEL_2.

volume is a pointer to an array of loudnesses in Decibel for piano, mezzo piano, mezzo forte, forte.

tuning is a pointer to an arrays of frequency dividers for the halftones of octave 0. The default value is ISO 16 pitch (A at 440 Hz) with equal tuning. Other tunings can be generated using the tool cvmtuning.

The number of sixteenth notes per second should divide the vertical retrace frequency evenly when playing pieces that have multiple voices, otherwise the voices can drift from each other. Depending on the ColecoVision the frequency is 50 or 60 Hz. Common values for sixteenth_notes_per_second are 5 and 10.

notes is a pointer to the tune.

The other two members are used by cvu_play_music() for internal purposed only and should be left at the values set by cvu_init_music().

The SN76489

The ColecoVision used a SN76489 sound chip, which offers four channels: Three tone channels and a noise channel. Only the tone channels are used for playing music here. The lowest frequency the SN76489 can produce is 109 Hz, just below the A in the small octave when using ISO pitch.

abc2cvm

abc2cvm translates music from the abc into one usable with libcvu.

The two most important options are -X, used to select a piece from an abc file containing many pieces and -V, which selects a voice. E.g. abc2cvm -X 4 -V 2 12duets.abc creates the notes array from the second voice of the fourth piece from the file 12duets.abc.

abc2cvm understands a subset of the abc standard. It tries to give an error message when it encounters something it doesn't understand in an abc file.

cvmtuning

While today the ISO 16 pitch of A at 440 Hz or nearby values are used nearly universally, different values have been used historically. cvmtuning can be used to generate tuning arrays for arbitrary pitches, so you can easily use e.g. the pitch of A at 480 Hz used for organs in Bach's times or the pitch of A at 380 Hz as used in early 18th century England.