atari-music-editor/index.php
2022-04-19 22:19:32 -07:00

457 lines
22 KiB
PHP
Executable File

<?php
/*
array format:
the ID of each button is the distortion and note number (from the Atari)
noteArray[distortion][atari note index][0] = MIDI file
noteArray[distortion][atari note index][1] = note name
noteArray[distortion][atari note index][2] = octave
noteArray[distortion][atari note index][3] = frequency
noteArray[distortion][atari note index][4] = error
noteArray[distortion][atari note index][5] = MIDI note index *these last three values are used when creating the new MIDI file
noteArray[distortion][atari note index][6] = pitch bend- left bits
noteArray[distortion][atari note index][7] = pitch bend- right bits
noteArray[distortion][atari note index][8] = color
*/
if (isset($_POST['format']))
{
$c1 = explode("\n",$_POST['c1_display']);
$c2 = explode("\n",$_POST['c2_display']);
$c1_data = array();
$c2_data = array();
for ($i = 0; $i < count($c1); $i++)
if (str_word_count($c1[$i],0,"#.0123456789") == 4)
array_push($c1_data,explode(" ",$c1[$i]));
for ($i = 0; $i < count($c2); $i++)
if (str_word_count($c2[$i],0,"#.0123456789") == 4)
array_push($c2_data,explode(" ",$c2[$i]));
$total = 0;
$measureval = $_POST['timesig_num']/$_POST['timesig_de'];
$measure = 1;
$newvalue1 = "-------$measure-------\n"; //the value of the textarea gets set to this after the formatting is complete
$newvalue2 = "-------$measure-------\n";
foreach ($c1_data as $i => $arr)
{
$total += 1/$arr[3];
$newvalue1 .= $arr[0]." ".$arr[1]." ".$arr[2]." ".$arr[3];
//echo $i." ".$total."<br />";
if ($total >= $measureval)
{
$total -= $measureval;
//add a bar and a measure number to the display
$measure++;
$newvalue1 .= "-------$measure-------\n";
}
}
//exit;
$measure = 1;
$total = 0;
foreach ($c2_data as $i => $arr)
{
$total += 1/$arr[3];
$newvalue2 .= $arr[0]." ".$arr[1]." ".$arr[2]." ".$arr[3];
if ($total >= $measureval)
{
$total -= $measureval;
//add a bar and a measure number to the display
$measure++;
$newvalue2 .= "-------$measure-------\n";
}
}
}
$dir = opendir("midi");
$note_array = array("square"=>array(),"lead"=>array(),"saw"=>array(),"bass"=>array());
while (($file = readdir($dir)) != null)
{
//format: distortion_atari note index_note name_octave_freq_error_MIDI note index_pitch bend left bits_pitch bend right bits
if (strpos($file,".ogg"))
{
$info = explode("_",$file);
$info[2] = str_replace("+","#",$info[2]);
$info[count($info)-1] = substr($info[count($info)-1],0,strpos($info[count($info)-1],".ogg"));
if (!isset($note_array[$info[0]][$info[1]]))
$note_array[$info[0]][$info[1]] = array();
array_push($note_array[$info[0]][$info[1]],$file);
for ($i = 2; $i < count($info); $i++)
array_push($note_array[$info[0]][$info[1]],$info[$i]);
$red = str_pad(dechex(100+2*$note_array[$info[0]][$info[1]][4]),2,"0",STR_PAD_LEFT);
$green = str_pad(dechex(150),2,"0",STR_PAD_LEFT);
$blue = str_pad(dechex(100-2*$note_array[$info[0]][$info[1]][4]),2,"0",STR_PAD_LEFT);
$color = $red.$green.$blue;
$note_array[$info[0]][$info[1]][8] = '#' . $color;
}
}
require ("createmidi.php");
?>
<!doctype html>
<html>
<head>
<title>Atari 2600 Music Utility</title>
<meta name='description' content='Enables easier composing of Atari 2600 music.' />
<meta name='keywords' content='atari, 2600, music, composing, midi' />
<link rel='stylesheet' type='text/css' href='style.css' />
<script type='text/javascript'>
var noteValue = 4;
var channel = 1;
var showColor = false;
var dotted = false;
var bufferCount = 1;
<?php
if (isset($_POST['format']))
echo "var bufferMin = 3;";
else
echo "var bufferMin = 2;";
?>
//these were made global because javascript annoys me
var activeKeyID = 0;
var mouseX = 0;
var mouseY = 0;
var bufferArray = new Array(2);
bufferArray[0] = new Array();
bufferArray[1] = new Array();
bufferArray[0][0] = "";
bufferArray[1][0] = "";
var noteArray = new Array(4);
for (i = 0; i < 4; i++)
{
noteArray[i] = new Array(32);
for (j = 0; j < 32; j++)
{
noteArray[i][j] = new Array(8);
for (k = 0; k < 8; k++)
noteArray[i][j][k] = null;
}
}
//populate noteArray, copying values from $note_array
<?php
foreach ($note_array as $dist => $a)
foreach ($a as $atariIndex => $b)
foreach ($b as $i => $c)
{
if ($i == 0 || $i == 1 || $i == 8)
$c = "'$c'"; //convert to string
if ($dist == "square")
echo "noteArray[0][$atariIndex][$i] = $c;\n\t\t";
else if ($dist == "lead")
echo "noteArray[1][$atariIndex][$i] = $c;\n\t\t";
else if ($dist == "saw")
echo "noteArray[2][$atariIndex][$i] = $c;\n\t\t";
else if ($dist == "bass")
echo "noteArray[3][$atariIndex][$i] = $c;\n\t\t";
}
?>
</script>
<script type='text/javascript' src='functions.js'></script>
</head>
<body>
<?php
$xstart = 10;
$ystart = 33;
$numoctaves = 7;
$octavestart = 1;
$wwidth = 20;
$wheight = 70;
$bwidth = 16;
$bheight = 45;
$keyarr = array("C","C#","D","D#","E","F","F#","G","G#","A","A#","B");
$wcount = 0;
$bcount = 0;
for ($i = 0; $i < 12*$numoctaves; $i++)
{
$index = $i%12;
$octave = $octavestart+floor($i/12);
if (strpos($keyarr[$index],"#"))
{
$space = 0;
switch ($bcount%5)
{
case 0: $space = ($wwidth-$bwidth/2)+($wwidth*7*floor($i/12)); break;
case 1: $space = ($wwidth*2-$bwidth/2)+($wwidth*7*floor($i/12)); break;
case 2: $space = ($wwidth*4-$bwidth/2)+($wwidth*7*floor($i/12)); break;
case 3: $space = ($wwidth*5-$bwidth/2)+($wwidth*7*floor($i/12)); break;
case 4: $space = ($wwidth*6-$bwidth/2)+($wwidth*7*floor($i/12)); break;
}
$class = "bkey";
$top = $ystart;
$left = $xstart+$space;
$width = $bwidth;
$height = $bheight;
$bcount++;
}
else
{
$class = "wkey";
$top = $ystart;
$left = $xstart+$wwidth*$wcount;
$wcount++;
$width = $wwidth;
$height = $wheight;
}
//determine the atari note index of the key in question for all four distortions
$noteName = $keyarr[$index].$octave;
$atariIndex = array("square"=>"nil","lead"=>"nil","saw"=>"nil","bass"=>"nil");
foreach ($note_array as $dist => $a)
foreach ($a as $aIndex => $b)
if ($b[1] == $noteName)
{
$atariIndex[$dist] = $aIndex;
$color = "#".$b[8];
}
if ($noteName == "C4")
$class .= " C4";
echo "\t<button id='square_$atariIndex[square]' name='key' class='$class' style='position: absolute; top: $top"."px; left: $left"."px; height: $height"."px; width: $width"."px;' onclick='playNote(id)' oncontextmenu='return false;' onmousedown='if (event.button == 2) showFloatingDiv(id,event);'></button>\n";
echo "\t<button id='lead_$atariIndex[lead]' name='key' class='$class' style='position: absolute; top: ".($top+($wheight+$ystart))."px; left: $left"."px; height: $height"."px; width: $width"."px;' onclick='playNote(id)' oncontextmenu='return false;' onmousedown='if (event.button == 2) showFloatingDiv(id,event);'></button>\n";
echo "\t<button id='saw_$atariIndex[saw]' name='key' class='$class' style='position: absolute; top: ".($top+2*($wheight+$ystart))."px; left: $left"."px; height: $height"."px; width: $width"."px;' onclick='playNote(id)' oncontextmenu='return false;' onmousedown='if (event.button == 2) showFloatingDiv(id,event);'></button>\n";
echo "\t<button id='bass_$atariIndex[bass]' name='key' class='$class' style='position: absolute; top: ".($top+3*($wheight+$ystart))."px; left: $left"."px; height: $height"."px; width: $width"."px;' onclick='playNote(id)' oncontextmenu='return false;' onmousedown='if (event.button == 2) showFloatingDiv(id,event);'></button>\n";
}
echo "\n\n\t<span style='font-weight: bold; font-size: 14pt; position: absolute; top: ".($top-25)."px; left: ".(-20+($xstart+$wwidth*$numoctaves*7)/2)."px;'>Square</span>";
echo "\n\t<span style='font-weight: bold; font-size: 14pt; position: absolute; top: ".($top+($wheight+$ystart)-25)."px; left: ".(-20+($xstart+$wwidth*$numoctaves*7)/2)."px;'>Lead</span>";
echo "\n\t<span style='font-weight: bold; font-size: 14pt; position: absolute; top: ".($top+2*($wheight+$ystart)-25)."px; left: ".(-20+($xstart+$wwidth*$numoctaves*7)/2)."px;'>Saw</span>";
echo "\n\t<span style='font-weight: bold; font-size: 14pt; position: absolute; top: ".($top+3*($wheight+$ystart)-25)."px; left: ".(-20+($xstart+$wwidth*$numoctaves*7)/2)."px;'>Bass</span>\n\n";
echo "\n\t<div align='center' style='margin-top: ".(4 * ($wheight + $ystart))."px; width: ".($xstart+$wwidth*$numoctaves*7)."px'>\n";
echo "\n\t<span style='text-align: left; position: absolute; left: $xstart"."px; top:".(4*($wheight+$ystart))."px'>";
echo "\n\t\t<label title='[c]'><input type='checkbox' accesskey='c' onclick='toggleColor()' autocomplete='off' />Color Me Blind</label>";
echo "\n\t</span>";
echo "\n\t<span style='text-align: left; position: absolute; left: ".(-35+$xstart+$numoctaves*7*$wwidth)."px; top:".(4*($wheight+$ystart))."px'>";
echo "\n\t\t<a title='[q]' accesskey='q' onfocus='showFAQ()' href='javascript:void(0)'>FAQ</a>";
echo "\n\t</span>";
?>
<table border='0' align='center'>
<tr><td colspan='2'>
<table align='center'>
<tr>
<td><button accesskey='1' name='note-select' title='whole note [1]' class='note-select' onclick='selectNote(1)'><img src='img/1.gif' /></button></td>
<td><button accesskey='2' name='note-select' title='half note [2]' class='note-select' onclick='selectNote(2)'><img src='img/2.gif' /></button></td>
<td><button accesskey='3' name='note-select' title='quarter note [3]' class='note-select' onclick='selectNote(4)' disabled='disabled' style='cursor: default;'><img src='img/4.gif' /></button></td>
<td><button accesskey='4' name='note-select' title='8th note [4]' class='note-select' onclick='selectNote(8)'><img src='img/8.gif' /></button></td>
<td><button accesskey='5' name='note-select' title='16th note [5]' class='note-select' onclick='selectNote(16)'><img src='img/16.gif' /></button></td>
<td><button accesskey='6' name='note-select' title='32nd note [6]' class='note-select' onclick='selectNote(32)'><img src='img/32.gif' /></button></td>
<td><button accesskey='7' name='note-select' title='64th note [7]' class='note-select' onclick='selectNote(64)'><img src='img/64.gif' /></button></td>
<td><label title='dot [w]'><input type='checkbox' accesskey='w' onclick='setDotted()' />Dotted</label></td>
</tr>
<tr>
<td align='center'><button name='note-select' title='whole rest' class='rest-select' onclick='writeRestData(1)'><img src='img/r1.gif' /></button></td>
<td align='center'><button name='note-select' title='half rest' class='rest-select' onclick='writeRestData(2)'><img src='img/r2.gif' /></button></td>
<td align='center'><button name='note-select' title='quarter rest' class='rest-select' onclick='writeRestData(4)'><img src='img/r4.gif' /></button></td>
<td align='center'><button name='note-select' title='8th rest' class='rest-select' onclick='writeRestData(8)'><img src='img/r8.gif' /></button></td>
<td align='center'><button name='note-select' title='16th rest' class='rest-select' onclick='writeRestData(16)'><img src='img/r16.gif' /></button></td>
<td align='center'><button name='note-select' title='32nd rest' class='rest-select' onclick='writeRestData(32)'><img src='img/r32.gif' /></button></td>
<td align='center'><button name='note-select' title='64th rest' class='rest-select' onclick='writeRestData(64)'><img src='img/r64.gif' /></button></td>
</tr>
</table>
</td></tr>
<form name='createMidiForm' action='index.php' method='post' style='display: inline;'>
<tr>
<td colspan='2' align='center'>
Tempo:
<input type='text' name='tempo' id='tempo' size='2' maxlength='3' value='<?php if (isset($_POST['tempo'])) echo $_POST['tempo']; else echo "120";?>' onblur='validateForm()' />
Time Signature:
<input type='text' name='timesig_num' id='timesig_num' size='1' maxlength='3' value='<?php if (isset($_POST['timesig_num'])) echo $_POST['timesig_num']; else echo "1";?>' onblur='validateForm()' />
/&nbsp;<select id='timesig_de' name='timesig_de'>
<option value='1' <?php if (isset($_POST['timesig_de']) && $_POST['timesig_de'] == 1) echo "selected='selected'";?>>1</option>
<option value='2' <?php if (isset($_POST['timesig_de']) && $_POST['timesig_de'] == 2) echo "selected='selected'";?>>2</option>
<option value='4' <?php if (isset($_POST['timesig_de']) && $_POST['timesig_de'] == 4) echo "selected='selected'";?>>4</option>
<option value='8' <?php if (isset($_POST['timesig_de']) && $_POST['timesig_de'] == 8) echo "selected='selected'";?>>8</option>
<option value='16' <?php if (isset($_POST['timesig_de']) && $_POST['timesig_de'] == 16) echo "selected='selected'";?>>16</option>
<option value='32' <?php if (isset($_POST['timesig_de']) && $_POST['timesig_de'] == 32) echo "selected='selected'";?>>32</option>
<option value='64' <?php if (isset($_POST['timesig_de']) && $_POST['timesig_de'] == 64) echo "selected='selected'";?>>64</option>
</select>
</td>
</tr>
<tr>
<td align='center'>
<label>
<input type='radio' name='channel_select' checked='checked' onclick='toggleChannel(1)' />
Channel 1
</label>
</td>
<td align='center'>
<label>
<input type='radio' name='channel_select' onclick='toggleChannel(2)' />
Channel 2
</label>
</td>
</tr>
<tr>
<td>
<textarea rows='20' cols='25' id='c1_display' name='c1_display'><?php if (isset($_POST['format'])) echo $newvalue1; else if (isset($_POST['c1_display'])) echo $_POST['c1_display']; ?></textarea>
</td>
<td>
<textarea rows='20' cols='25' id='c2_display' name='c2_display'><?php if (isset($_POST['format'])) echo $newvalue2; else if (isset($_POST['c2_display'])) echo $_POST['c2_display'];?></textarea>
</td>
</tr>
<tr>
<td align='center'><input style='cursor: pointer;' type='button' value='Clear' onclick='clearChannelData(1)' title='Clear channel 1 data'></input></td>
<td align='center'><input style='cursor: pointer;' type='button' value='Clear' onclick='clearChannelData(2)' title='Clear channel 2 data'></input></td>
</tr>
<tr><td colspan='2' align='center'>
<input style='color:#339966; font-family: Georgia; font-size: 150%; cursor: pointer;' type='submit' name='submit' value='Listen' title='Download the MIDI file'></input>
<input accesskey='f' style='color:#339966; font-family: Georgia; font-size: 150%; cursor: pointer;' type='submit' name='format' value='Format' title='Partition the measures [f]'></input>
<input accesskey='z' style='color:#339966; font-family: Georgia; font-size: 150%; cursor: pointer;' type='button' name='undo' value='Undo' onclick='undoLastKeypress()' title='Undo last keypress [z]'></input>
</td></tr>
</form>
</table>
</div>
<div id='floatDiv' class='floater' onclick='hideFloatingDiv()'></div>
<!-- FAQ -->
<div id='faq' class='faq' onclick='hideFAQ()'>
<h3>Shut the FAQ up</h3><br />
<span class='faq_q'>How do I close this pink window?</span>
<blockquote class='faq_a'>Click anywhere inside it.</blockquote>
<span class='faq_q'>Who? | What? | When? | Where? | Why?</span>
<blockquote class='faq_a'>
Tommy Montgomery |
Atari 2600 Music Composing Utility |
January, 2007 |
On the internet |
Why not
</blockquote>
<span class='faq_q'>How?</span>
<blockquote class='faq_a'>
An unholy union of PHP, Javascript and the HTML DOM. The MIDI files used to
play the sounds were created using Java because I didn't know PHP
could write to binary files at the time. 15 years later in April 2022
I converted them to OGG.
</blockquote>
<span class='faq_q'>Why is this useful?</span>
<blockquote class='faq_a'>
Writing music for the 2600 is difficult, because of the variations in
pitch. The frequencies don't conform to any sort of useful or intelligible
scale, so we have to approximate to get what we want. This utility makes
that process easier, by supplying a visual and aural cue of the
(musical) limitations of the 2600.
</blockquote>
<span class='faq_q'>Why are there four keyboards?</span>
<blockquote class='faq_a'>
The four keyboards correspond to the four "melodic" distortions (meaning
the ones that don't sound like noise). They are labeled for your
convenience. The grayed keys are notes whose frequencies are
unavailable in that distortion. Middle C is outlined in red.
</blockquote>
<span class='faq_q'>What about the other distortions?</span>
<blockquote class='faq_a'>
The primary reason I didn't include them is because there aren't any
MIDI voices that can approximate them. The secondary reason is because
this utility is for making <em>music</em>, not <em>noise</em>, which is
generally what the other distortions are used for; making noise is generally
much easier and doesn't need a fancy tool.
</blockquote>
<span class='faq_q'>How do I write songs?</span>
<blockquote class='faq_a'>
The Atari 2600 has two sound channels available (or so I've heard). The
two textareas at the bottom of the screen correspond to each channel. They are
labeled for your convenience. When you click on a key, you will hear its sound and the
data will be written to the channel's textarea (that data can also be typed in
manually). To listen to your song, press "Listen." The best way to learn how
to use this is to experiment. Use the "Format" and "Listen" buttons liberally.
</blockquote>
<span class='faq_q'>What does "Format" do?</span>
<blockquote class='faq_a'>
Pressing this button formats each channel's data into something more
comprehensible. It groups all the data into measures (this is dependent
on the time signature).
</blockquote>
<span class='faq_q'>How do I change the note value/channel/tempo/time signature?</span>
<blockquote class='faq_a'>
Find the labels and/or pictures and click something until it does what you want.
</blockquote>
<span class='faq_q'>How do I insert a rest in my song?</span>
<blockquote class='faq_a'>
See above.
</blockquote>
<span class='faq_q'>What does "Dotted" do?</span>
<blockquote class='faq_a'>
Checking this elongates the note by 50%.
</blockquote>
<span class='faq_q'>What do all the numbers in the textareas mean?</span>
<blockquote class='faq_a'>
<ul>
<li>The first thing is the note name and octave (e.g. <code>E3</code>).</li>
<li>The second thing is the distortion (<code>0</code> = square, <code>1</code> =
lead, <code>2</code> = saw, <code>3</code> = bass. Note that these do <em>not</em>
correspond to the distortion numbers in the text file linked to below).</li>
<li>The third thing is the pitch index, mentioned below.</li>
<li>The last thing is the note value (<code>1</code> = whole note, <code>2</code> =
half note, <code>4</code> = quarter note, etc.).</li>
</ul>
</blockquote>
<span class='faq_q'>I know stuff about some stuff. How do I look at this stuff?</span>
<blockquote class='faq_a'>
Right click on a key to view info about that note (doesn't work in Opera). <code>Pitch</code> is the
Atari 2600 pitch index (see <a href='http://home.arcor.de/estolberg/texts/freqform.txt'>here</a>
for details); <code>Freq</code> is the note's frequency; <code>Error</code> is the
distance in cents from "perfect" pitch. If you don't know what that means, the closer
to 0 is better. Unless you're doing something weird and/or painful, you'll want
all the notes in your song to have approximately the same error.
</blockquote>
<span class='faq_q'>What does "Exchange" mean?</span>
<blockquote class='faq_a'>
Some notes have more than one frequency that approximates that note. Clicking on
"Exchange" toggles between the two. Such notes are generally in the lower half
of the keyboard.
</blockquote>
<span class='faq_q'>What does "Color Me Blind" do?</span>
<blockquote class='faq_a'>
Checking this color codes all the keys. Keys with the same color are in tune
with each other. Keys that are different colors probably shouldn't be used in
succession.
</blockquote>
<span class='faq_q'>Dude, seriously. Using the mouse is a bother. Are there any keyboard shortcuts?</span>
<blockquote class='faq_a'>
Glad you asked. If you hover the cursor over various objects on the page, you'll notice
a single character in square brackets in the tooltip for some of them (try one of the
note value buttons, for example). Check
<a href="https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/accesskey">here</a>
to see your access key combination (generally <code>ALT</code> or <code>ALT+SHIFT</code>).
</blockquote>
</div>
<!-- END FAQ -->
<script type='text/javascript'>disableNils();</script>
<audio id='sound'></audio>
</body>
</html>