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 sin
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("volumeis
too high"); return; } cmb.Data2 = vol; cmb.Build(); outDevice.Send(cmb.Result); } } }private
void
Form1_FormClosing(object sender, FormClosingEventArgs e) { outDevice.Dispose(); } } }