Working on integrating music.

This commit is contained in:
Martin Haye 2018-03-20 07:58:59 -07:00
parent f1e15d822e
commit 0693ea2fec
4 changed files with 109 additions and 8 deletions

View File

@ -8,7 +8,7 @@ optarg = 1
timescale = 16.0 # Scale time to 16th of a second
extperchan = 9 # Default to standard MIDI channel 10 for extra percussion
if len(sys.argv) == 1:
print 'Usage:', sys.argv[0], '[-p extra_percussion_channel] [-t timescale] MIDI_file'
print('Usage:', sys.argv[0], '[-p extra_percussion_channel] [-t timescale] MIDI_file')
sys.exit(0)
# Parse optional arguments
while optarg < (len(sys.argv) - 1):
@ -33,6 +33,7 @@ for msg in mid:
onote = int(msg.note % 12)
lrchan = int(msg.channel & 1)
vol = int(msg.velocity >> 3)
print('type=%s onote=%d lrchan=%d vol=%d' % (msg.type, onote, lrchan, vol))
if msg.velocity > 0 and vol == 0:
vol = 1
if msg.type == 'note_off':
@ -45,15 +46,15 @@ for msg in mid:
# Percussion
#
if vol > 0:
print '\t!BYTE\t${0:02X}, ${1:02X}, ${2:02X}\t; Percussion {3:d} Chan {4:d} Dur {5:d}'.format(deltatime, msg.note ^ 0x40, (lrchan << 7) | vol, msg.note, msg.channel + 1, vol)
print('\t!BYTE\t${0:02X}, ${1:02X}, ${2:02X}\t; Percussion {3:d} Chan {4:d} Dur {5:d}'.format(deltatime, msg.note ^ 0x40, (lrchan << 7) | vol, msg.note, msg.channel + 1, vol))
if extperchan == 9: # Play percussion on both channels if no extended percussion
print '\t!BYTE\t${0:02X}, ${1:02X}, ${2:02X}\t; Percussion {3:d} Chan {4:d} Dur {5:d}'.format(0, msg.note ^ 0x40, vol, msg.note, msg.channel + 1, vol)
print('\t!BYTE\t${0:02X}, ${1:02X}, ${2:02X}\t; Percussion {3:d} Chan {4:d} Dur {5:d}'.format(0, msg.note ^ 0x40, vol, msg.note, msg.channel + 1, vol))
eventtime = 0.0
else:
#
# Note
#
print '\t!BYTE\t${0:02X}, ${1:02X}, ${2:02X}\t; Note {3:d} Chan {4:d} Vol {5:d}'.format(deltatime, 0x80 | (octave << 4) | onote, (lrchan << 7) | vol, msg.note, msg.channel + 1, vol)
print('\t!BYTE\t${0:02X}, ${1:02X}, ${2:02X}\t; Note {3:d} Chan {4:d} Vol {5:d}'.format(deltatime, 0x80 | (octave << 4) | onote, (lrchan << 7) | vol, msg.note, msg.channel + 1, vol))
eventtime = 0.0
elif msg.type == 'set_tempo':
pass
@ -65,4 +66,4 @@ for msg in mid:
pass
elif msg.type == 'program_change':
pass
print '\t!BYTE\t${0:02X}, $00, $00'.format(int(eventtime + 0.5))
print('\t!BYTE\t${0:02X}, $00, $00'.format(int(eventtime + 0.5)))

View File

@ -23,6 +23,12 @@ import java.util.Calendar
import java.util.zip.GZIPInputStream
import java.util.LinkedHashMap
import java.security.MessageDigest
import javax.sound.midi.MidiEvent;
import javax.sound.midi.MidiMessage;
import javax.sound.midi.MidiSystem;
import javax.sound.midi.Sequence;
import javax.sound.midi.ShortMessage;
import javax.sound.midi.Track;
import javax.xml.bind.DatatypeConverter
import groovy.json.JsonOutput
import groovy.util.Node
@ -46,6 +52,7 @@ class A2PackPartitions
def TYPE_BYTECODE = 9
def TYPE_FIXUP = 10
def TYPE_PORTRAIT = 11
def TYPE_SONG = 12
static final int FLOPPY_SIZE = 35*8 // good old 140k floppy = 280 blks
static final int AC_KLUDGE = 2 // minus 1 to work around last-block bug in AppleCommander
@ -63,7 +70,8 @@ class A2PackPartitions
8: "Code",
9: "Code",
10: "Code",
11: "Portrait image"]
11: "Portrait image",
12: "Song"]
def typeNameToNum = ["code": TYPE_CODE,
"map2D": TYPE_2D_MAP,
@ -75,7 +83,8 @@ class A2PackPartitions
"module": TYPE_MODULE,
"bytecode": TYPE_BYTECODE,
"fixup": TYPE_FIXUP,
"portrait": TYPE_PORTRAIT]
"portrait": TYPE_PORTRAIT,
"song": TYPE_SONG]
def typeNumToName = [1: "code",
2: "map2D",
@ -87,7 +96,8 @@ class A2PackPartitions
8: "module",
9: "bytecode",
10: "fixup",
11: "portrait"]
11: "portrait",
12: "song"]
def mapNames = [:] // map name (and short name also) to map.2dor3d, map.num
def sysCode = [:] // memory manager
@ -103,6 +113,7 @@ class A2PackPartitions
def frames = [:] // img name to img.num, img.buf
def portraits = [:] // img name to img.num, img.buf
def fonts = [:] // font name to font.num, font.buf
def songs = [:] // song name to song.num, song.buf
def modules = [:] // module name to module.num, module.buf
def bytecodes = [:] // module name to bytecode.num, bytecode.buf
def fixups = [:] // module name to fixup.num, fixup.buf
@ -1171,6 +1182,87 @@ class A2PackPartitions
return [num, module.locationsWithTriggers]
}
def packSong(File midiFile)
{
final int NOTE_ON = 0x90;
final int NOTE_OFF = 0x80;
Sequence sequence = MidiSystem.getSequence(midiFile)
def outBuf = ByteBuffer.allocate(50000)
int extperchan = 9 // this basically means no extra percussion channel, since perc is 9 anyway
int trackNumber = 0
for (Track track : sequence.getTracks()) {
trackNumber++
System.out.println("Track " + trackNumber + ": size = " + track.size())
System.out.println()
int totaltime = 0
for (int i=0; i < track.size(); i++) {
MidiEvent event = track.get(i)
System.out.print("@" + event.getTick() + " ")
MidiMessage message = event.getMessage()
if (message instanceof ShortMessage) {
ShortMessage sm = (ShortMessage) message
System.out.print("Channel: " + sm.getChannel() + " ")
if (sm.getCommand() == NOTE_ON || sm.getCommand() == NOTE_OFF) {
int deltatime = event.getTick() - totaltime
int key = sm.getData1()
int octave = (key / 12)-1
int onote = key % 12
int lrchan = sm.getChannel() & 1
int velocity = sm.getData2()
int vol = velocity >> 3
System.out.println(((sm.getCommand() == NOTE_ON) ? "on" : "off") +
"onote/oct=" + onote + "/" + octave +
" channel=" + sm.getChannel() + " lrchan=" + lrchan +
" key=" + key + " velocity=" + velocity + " vol=" + vol)
if (velocity > 0 && vol == 0)
vol = 1
if (sm.getCommand() == NOTE_OFF)
vol = 0
if (octave < 0)
octave = 0
totaltime = event.getTick()
if (sm.getChannel() == 9 || sm.getChannel() == extperchan)
{
// Percussion
if (vol > 0) {
outBuf.put((byte)deltatime)
outBuf.put((byte)(note ^ 0x40))
outBuf.put((byte)((lrchan << 7) | vol))
outBuf.put((byte)(msg.channel + 1))
outBuf.put((byte)vol)
if (extperchan == 9) { // Play percussion on both channels if no extended percussion
outBuf.put((byte)0)
outBuf.put((byte)(note ^ 0x40))
outBuf.put((byte)vol) // omits channel
}
}
}
else {
// Note
outBuf.put((byte)deltatime)
outBuf.put((byte)(0x80 | (octave << 4) | onote))
outBuf.put((byte)((lrchan << 7) | vol))
}
} else {
System.out.println("Command:" + sm.getCommand())
}
} else {
System.out.println("Other message: " + message.getClass())
}
}
System.out.println()
}
def num = songs.size() + 1
songs[midiFile.name] = [num:num, buf:compress(unwrapByteBuffer(outBuf))]
addResourceDep("map", "<root>", "song", midiFile.name)
}
def readBinary(path)
{
def f = new File(path)
@ -1769,6 +1861,7 @@ class A2PackPartitions
recordChunks("frame", frames)
recordChunks("portrait", portraits)
recordChunks("font", fonts)
recordChunks("song", songs)
recordChunks("module", modules)
recordChunks("bytecode", bytecodes)
recordChunks("fixup", fixups)
@ -2379,6 +2472,11 @@ class A2PackPartitions
numberGlobalTiles(dataIn)
packGlobalTileSet(dataIn)
// Play around with music
def midiFile = new File(xmlFile.getParentFile(), "song.mid")
if (midiFile.exists())
packSong(midiFile)
// Divvy up the images by category
def titleImgs = []
def uiFrameImgs = []

View File

@ -163,6 +163,7 @@ RES_TYPE_MODULE = 8
RES_TYPE_BYTECODE = 9
RES_TYPE_FIXUP = 10
RES_TYPE_PORTRAIT = 11
RES_TYPE_SONG = 12
;------------------------------------------------------------------------------
; Command codes

View File

@ -45,6 +45,7 @@ const RES_TYPE_MODULE = 8
const RES_TYPE_BYTECODE = 9
const RES_TYPE_FIXUP = 10
const RES_TYPE_PORTRAIT = 11
const RES_TYPE_SONG = 12
// Command codes
const RESET_MEMORY = $10