SoundOfLife

Form1.cs
Form1.Designer.cs
Program.cs
Properties\AssemblyInfo.cs
Properties\Resources.Designer.cs
Properties\Settings.Designer.cs

using

System;

using

System.Collections.Generic;

using

System.ComponentModel;

using

System.Data;

using

System.Drawing;

using

System.Drawing.Drawing2D;

using

System.Text;

using

System.Windows.Forms;

using

System.Threading;

using

Multimedia.Midi;

using

LSCollections.Immutable;

namespace

SoundOfLife {

public

partial

class

Form1 : Form {

private

bool[,] board;

private

int

width = 24;

private

int

height = 24;

// only create 1 output device, because only 1 exists

private

static

OutputDevice outDevice =

new

OutputDevice(0);

public

Form1() { InitializeComponent();

// the playing board is just an array of bools. true means alive,

// false means dead

board =

new

bool[width, height];

foreach

(string s

in

Enum.GetNames(typeof(GeneralMidiInstrument))) { comboBox1.Items.Add(s); comboBox2.Items.Add(s); comboBox3.Items.Add(s); comboBox4.Items.Add(s); } comboBox1.SelectedIndex = 4; comboBox2.SelectedIndex = 14; comboBox3.SelectedIndex = 24; comboBox4.SelectedIndex = 34; ChannelMessageBuilder cmb =

new

ChannelMessageBuilder(); cmb.Command = ChannelCommand.NoteOn; cmb.MidiChannel = 0; cmb.Data1 = 60; cmb.Data2 = 127; cmb.Build();

// useful for debugging on machines with different midi synths

//MidiOutCaps moc = OutputDevice.GetDeviceCapabilities(0);

//MessageBox.Show(moc.voices.ToString());

}

// this ought to be made more efficient, but it's pretty primitive drawing,

// so it doesn't really matter

private

void

panel1_Paint(object sender, PaintEventArgs e) {

// Draw some background colors

Rectangle r =

new

Rectangle(0, 0, 120, 480); e.Graphics.FillRectangle(new LinearGradientBrush(r, Color.White, Color.Red, 90), r); r =

new

Rectangle(120, 0, 120, 480); e.Graphics.FillRectangle(new LinearGradientBrush(r, Color.White, Color.Blue, 90), r); r =

new

Rectangle(240, 0, 120, 480); e.Graphics.FillRectangle(new LinearGradientBrush(r, Color.White, Color.Green, 90), r); r =

new

Rectangle(360, 0, 120, 480); e.Graphics.FillRectangle(new LinearGradientBrush(r, Color.White, Color.Purple, 90), r);

// Draw the grid

for

(int i = 0; i <= width; i++) {

int

x = i * 20; e.Graphics.DrawLine(new Pen(Color.Black), x, 0, x, panel1.Height); e.Graphics.DrawLine(new Pen(Color.Black), 0, x, panel1.Width, x); }

// draw the alive/dead cells

for

(int i = 0; i < width; i++) {

for

(int j = 0; j < height; j++) {

if

(board[i, j]) e.Graphics.FillRectangle(new SolidBrush(Color.Black), i * 20, j * 20, 20, 20); } } }

private

void

timer1_Tick(object sender, EventArgs e) {

// create a new board so we don't erase the old one while it's updating

bool[,] new_board =

new

bool[width, height];

for

(int i = 0; i < width; i++) {

for

(int j = 0; j < height; j++) {

// Check the spaces around this one for the life/death rules

int

count = 0;

try

{

if

(board[i - 1, j - 1]) count++;

if

(board[i - 1, j]) count++;

if

(board[i - 1, j + 1]) count++;

if

(board[i, j - 1]) count++;

if

(board[i, j + 1]) count++;

if

(board[i + 1, j - 1]) count++;

if

(board[i + 1, j]) count++;

if

(board[i + 1, j + 1]) count++; }

catch

(IndexOutOfRangeException ioore) {

// don't worry about it

}

// apply the life/death rules

if

(board[i, j] && count < 2) new_board[i, j] = false;

else

if

(board[i, j] && count > 3) new_board[i, j] = false;

else

if

(board[i, j] && (count == 2 || count == 3)) new_board[i, j] = true;

else

if

(!board[i, j] && count == 3) new_board[i, j] = true; } }

// update the audio once/tick

//PlaySounds();

int[] inst = { comboBox1.SelectedIndex, comboBox2.SelectedIndex, comboBox3.SelectedIndex, comboBox4.SelectedIndex }; PlaySounds(inst); board = new_board; panel1.Invalidate(); }

private

void

button1_Click(object sender, EventArgs e) { timer1.Enabled = true; }

private

void

panel1_MouseClick(object sender, MouseEventArgs e) {

if

(timer1.Enabled) return;

// Convert mouse coordinates to a grid position

int

x = e.X / 20;

int

y = e.Y / 20; board[x, y] = !board[x, y];

//MessageBox.Show("clicked, x: " + x.ToString() + ", y: " + y.ToString());

panel1.Invalidate(); }

// stop the timer and turn all the sound off

private

void

button2_Click(object sender, EventArgs e) { ChannelMessageBuilder cmb =

new

ChannelMessageBuilder(); cmb.MidiChannel = 0; cmb.Command = ChannelCommand.NoteOff; cmb.Build(); outDevice.Send(cmb.Result); timer1.Enabled = false; }

// test tone output

private

void

button3_Click(object sender, EventArgs e) { ChannelMessageBuilder builder =

new

ChannelMessageBuilder(); builder.Command = ChannelCommand.ProgramChange; builder.MidiChannel = 0;

// Data1 is the instrument in ProgramChange command

builder.Data1 = 20; builder.Build(); outDevice.Send(builder.Result); builder.Command = ChannelCommand.NoteOn; builder.MidiChannel = 0;

// Data1 is the note in a NoteOn command

builder.Data1 = 50;

// Data2 is the volume

builder.Data2 = 127; builder.Build(); outDevice.Send(builder.Result); Thread.Sleep(1000); builder.Command = ChannelCommand.NoteOff; builder.Data2 = 0; builder.Build(); outDevice.Send(builder.Result); }

// render midi audio

private

void

PlaySounds(int[] instruments) { ChannelMessageBuilder cmb =

new

ChannelMessageBuilder(); cmb.MidiChannel = 0;

// iterate through the instruments

for

(int k = 0; k < instruments.Length; k++) {

// set the instrument

cmb.Command = ChannelCommand.ProgramChange; cmb.Data1 = instruments[k]; cmb.Build(); outDevice.Send(cmb.Result);

// work through the column that represents the current instrument

for

(int i = 0; i < height; i++) { cmb.Command = ChannelCommand.NoteOn; cmb.Data1 = i + 40;

int

vol = 0;

//int j = 6 * k;

int

start = 6 * k;

int

stop = start + 6;

for

(int j = start; j < stop; j++) {

if

(board[j, i]) vol += 21; }

if

(vol > 127) { MessageBox.Show("volume

is

too high"); return; } cmb.Data2 = vol; cmb.Build(); outDevice.Send(cmb.Result); } } }

private

void

Form1_FormClosing(object sender, FormClosingEventArgs e) { outDevice.Dispose(); } } }