As promised, my EncodeMusic class, which generates the “music” format that Sound4K plays. I’ve tried to comment it so that you can understand what’s going on, but you’re welcome ask questions or just use it without understanding.
package ca.townsends.games.templates;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.HashMap;
import java.util.StringTokenizer;
/**
* Takes a csv-like file of notes in {duration}{octave}{note} format, and outputs one byte per note. The input
* ignores all whitespace, including linefeeds and carriage returns, so you can group music as you please. For example,
* the first few stanzas of the song "Music Box Dancer" are:
*
* 13C,13E,13G,13E,14C,13G,13E,13G,
* 13C,13E,13G,13E,14C,13G,13E,13G,
* 13C,13E,13G,13E,14C,13G,13E,13G,
* 13C,13E,13G,13E,14C,13G,13E,13G,
*
* 14C,13G,14C,14E,14C,14E,14G,14C,
* 15C,15B,15A,34G,20R,
* 14G,14F,14D,14B,13G,14C,14D,14F,
* 14E,14C,15A,34G,20R,
* 14C,13G,14C,14E,14C,14E,14G,14C,
* 15C,15B,15A,34G,20R,
*
* Note that the input accepts notes in any mixed-case, and recognizes both sharps
* and flats (e.g.: "A", "C#" and "Db" are all valid notes within an octave.) "R" is used for a Rest note.
*
* I've named the output format "ca.townsends.music", with a ".music" extension, but that's purely arbitrary.
*
*
* @author Rick Townsend
* @version 1.1
* 2012-02-25
*/
public class EncodeMusic {
/** Notes are between 0 and 63:
* - Shifting down by 40 gives a range of -40 to +22, with 23 reserved for silent beat.
* - Since -9 is middle C, that leaves (-40 - -9) = 31 notes below middle C, and (22 - -9) = 31 notes above middle C
* - This also means incoming, encoded notes should have 31 as middle C (so it translates to shifted value -9).
*/
public static final int NOTE_SHIFT = -40;
/**
* Main method
* @param args Not used
*/
public static void main(String[] args) {
HashMap<String,Integer> noteMap = new HashMap<String,Integer>();
// The recognized notes within an octave
noteMap.put("A", 0);
noteMap.put("A#", 1);
noteMap.put("Bb", 1);
noteMap.put("B", 2);
noteMap.put("C", 3);
noteMap.put("C#", 4);
noteMap.put("Db", 4);
noteMap.put("D", 5);
noteMap.put("D#", 6);
noteMap.put("Eb", 6);
noteMap.put("E", 7);
noteMap.put("F", 8);
noteMap.put("F#", 9);
noteMap.put("Gb", 9);
noteMap.put("G", 10);
noteMap.put("G#", 11);
noteMap.put("Ab", 11);
noteMap.put("R", 63); // Rest note
InputStream inStream = null;
OutputStream outStream = null;
// If we get an IO exception at any time, there's no point in continuing.
try {
// You could change these to be parameters from the args[] array, instead of hard-coded.
inStream = EncodeMusic.class.getResourceAsStream("MusicBoxDancer.csv");
outStream = new FileOutputStream("src/ca/townsends/games/templates/outputMusic.music");
byte[] bytes = new byte[4096 * 4]; // 4K notes is a very, very long song... But if you overrun it, just make this bigger.
// Grab all the bytes from the file
inStream.read(bytes);
// Dump it to a String so we can parse it more easily
String data = (new String(bytes)).toUpperCase();
// Split on commas
StringTokenizer st = new StringTokenizer(data, ",");
// Parse all the notes
while(st.hasMoreTokens()) {
// Trim off whitespace
String token = st.nextToken().trim();
// Skip any empty values (ie, where the input was only whitespace between two commas, or simply ",,")
if (token.length() == 0) continue;
// Grab the duration, octave and note
int inDuration = Integer.parseInt(token.substring(0, 1));
int inOctave = Integer.parseInt(token.substring(1, 2));
String inNote = token.substring(2); // This grabs all text from the third character onward, so it will get both single ("C") and double ("C#) character notes
//System.out.println(inNote);
// Grab the notes value from the map
int noteVal = noteMap.get(inNote);
// Calculate the actual note value (0 - 62), by multiplying by the octave and shifting down by 7 so that middle C is dead center at note 31.
int outNote = (byte)(inOctave * 12 + noteVal - 7);
// Shift the duration up by 6 bits
int duration = inDuration << 6;
// Hard-code the Rest note as note 63.
if (noteVal == 63) outNote = 63;
// Write the note in the lower 6 bits and the duration in the top 2 bits to both the output stream and system.out
outStream.write((byte)(outNote | duration));
System.out.print((byte)(outNote | duration) + ",");
//System.out.print((char)(outNote | duration));
//System.out.print("0x" + Integer.toHexString(outNote | duration).toUpperCase() + ",");
}
} catch (IOException e) {
e.printStackTrace();
} finally {
try {inStream.close();} catch (Exception e){};
try {outStream.close();} catch (Exception e){};
}
}
}