Improved song conversion and playback.

This commit is contained in:
Martin Haye 2018-03-26 13:48:10 -07:00
parent c819c30b34
commit faa5270a0c
3 changed files with 128 additions and 55 deletions

View File

@ -33,14 +33,15 @@ 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))
print('time=%.4f type=%s octave=%d onote=%d lrchan=%d vol=%d' % (msg.time, msg.type, octave, onote, lrchan, vol))
if msg.velocity > 0 and vol == 0:
vol = 1
if msg.type == 'note_off':
vol = 0
if octave < 0:
octave = 0
totaltime += deltatime
totaltime += eventtime
print('totaltime=%.4f scaled=%.4f' % (totaltime, totaltime*16/16.24))
if msg.channel == 9 or msg.channel == extperchan:
#
# Percussion

View File

@ -23,6 +23,7 @@ import java.util.Calendar
import java.util.zip.GZIPInputStream
import java.util.LinkedHashMap
import java.security.MessageDigest
import javax.sound.midi.MetaMessage;
import javax.sound.midi.MidiEvent;
import javax.sound.midi.MidiMessage;
import javax.sound.midi.MidiSystem;
@ -1188,67 +1189,37 @@ class A2PackPartitions
final int NOTE_OFF = 0x80;
Sequence sequence = MidiSystem.getSequence(midiFile)
//System.out.format("Sequence: div=%.2f PPQ=%.2f res=%d\n", sequence.divisionType,
// sequence.PPQ, sequence.resolution)
def outBuf = ByteBuffer.allocate(50000)
int extperchan = 9 // this basically means no extra percussion channel, since perc is 9 anyway
int trackNumber = 0
def notesByTime = [:]
for (Track track : sequence.getTracks()) {
trackNumber++
//System.out.println("Track " + trackNumber + ": size = " + track.size())
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 time = event.getTick() * 16 / 200
int deltatime = time - 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
totaltime = time
//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
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())
int tickTrack = (event.tick << 8) | (trackNumber)
if (!notesByTime.containsKey(tickTrack))
notesByTime[tickTrack] = []
notesByTime[tickTrack] << message
} else if (message instanceof MetaMessage) {
if (message.getType() == 0x51) {
int usecPerBeat = ((message.getData()[0] & 0xFF) << 16) |
((message.getData()[1] & 0xFF) << 8) |
(message.getData()[2] & 0xFF)
//System.out.println("Tempo message: " + usecPerBeat)
}
else if (message.getType() == 127) {
//System.out.println("Meta message: type=127 data=...")
}
else {
//System.out.println("Meta message: type=" + message.getType() + " data=" + message.getData())
}
} else {
//System.out.println("Other message: " + message.getClass())
@ -1256,6 +1227,64 @@ class A2PackPartitions
}
}
int totaltime = 0
notesByTime.keySet().sort().each { int tickTrack ->
int tick = tickTrack >> 8
trackNumber = tickTrack & 0xFF
notesByTime[tickTrack].each { ShortMessage sm ->
//System.out.print("tick=" + tick + " track=" + trackNumber + " channel=" + sm.getChannel() + " ")
if (sm.getCommand() == NOTE_ON || sm.getCommand() == NOTE_OFF) {
int time = tick * 120.0 / sequence.resolution * 16 / 203
int deltatime = time - 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
totaltime = time
//System.out.println(((sm.getCommand() == NOTE_ON) ? "on" : "off") +
// " time=" + time +
// " 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
if (vol > 0)
vol = (vol >> 2) + 12
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())
}
}
}
// Compress and store the resulting buffer
def num = songs.size() + 1
songs[midiFile.name] = [num:num, buf:compress(unwrapByteBuffer(outBuf))]
addResourceDep("map", "<root>", "song", midiFile.name)
@ -2472,7 +2501,7 @@ class A2PackPartitions
packGlobalTileSet(dataIn)
// Play around with music
def midiFile = new File(xmlFile.getParentFile(), "song.mid")
def midiFile = new File(xmlFile.getAbsoluteFile().getParentFile(), "song.mid")
if (midiFile.exists())
packSong(midiFile)

View File

@ -16,7 +16,7 @@ const rndseed = $004E
const LSB = 0
const MSB = 1
const MB_ARPEGGIO = 4 // In 16ths of a second
const MAX_MBCH_NOTES = 9
const MAX_MBCH_NOTES = 3
const SPKR_ARPEGGIO = 2 // In 16ths of a second
const DUR16TH = 8
const MAX_SPKR_NOTES = 4
@ -95,6 +95,7 @@ word seqTrack, seqEvent, seqTime, eventTime, updateTime
byte numNotes, seqRepeat
byte indexA[2], indexB[2], indexC[2]
byte noteA[2], noteB[2], noteC[2]
byte volA[2], volB[2], volC[2]
word notes1[MAX_MBCH_NOTES], notes2[MAX_MBCH_NOTES]
word notes[2] = @notes1, @notes2
word periods1[MAX_MBCH_NOTES], periods2[MAX_MBCH_NOTES]
@ -513,6 +514,9 @@ def mbSequence(yield, func)#0
noteA[0] = 0; noteA[1] = 0
noteB[0] = 0; noteB[1] = 0
noteC[0] = 0; noteC[1] = 0
volA[0] = 0; volA[1] = 0
volB[0] = 0; volB[1] = 0
volC[0] = 0; volC[1] = 0
//
// Get the PSGs ready
//
@ -639,6 +643,7 @@ def mbSequence(yield, func)#0
if n.LSB <> noteA[channel]
psgWriteTone(mbVIAs[channel], AFREQ, periods[channel, i], n.MSB)
noteA[channel] = n.LSB
volA[channel] = n.MSB | $80
indexA[channel] = i
fin
//
@ -655,6 +660,7 @@ def mbSequence(yield, func)#0
if n.LSB <> noteB[channel]
psgWriteTone(mbVIAs[channel], BFREQ, periods[channel, i], n.MSB)
noteB[channel] = n.LSB
volB[channel] = n.MSB | $80
indexB[channel] = i
fin
//
@ -672,6 +678,7 @@ def mbSequence(yield, func)#0
psgWrite(mbVIAs[channel], MIXER, $38) // Tone on C, B, A
psgWriteTone(mbVIAs[channel], CFREQ, periods[channel, i], n.MSB)
noteC[channel] = n.LSB
volC[channel] = n.MSB | $80
indexC[channel] = i
fin
next
@ -681,9 +688,45 @@ def mbSequence(yield, func)#0
// Increment time tick
//
seqTime++
while !(mbVIA1->IFR & $40) // Wait for T1 interrupt
period = 0
repeat
if a2keypressed(); quit = TRUE; break; fin
loop
period++
if (period & 63) == 1
for channel = 0 to 1
if volA[channel] & $80
volA[channel] = volA[channel] & $7F
elsif volA[channel]
volA[channel]--
psgWrite(mbVIAs[channel], AENVAMP, volA[channel])
if volA[channel] == 0
noteA[channel] = 0
numNotes--
fin
fin
if volB[channel] & $80
volB[channel] = volB[channel] & $7F
elsif volB[channel]
volB[channel]--
psgWrite(mbVIAs[channel], BENVAMP, volB[channel])
if volB[channel] == 0
noteB[channel] = 0
numNotes--
fin
fin
if volC[channel] & $80
volC[channel] = volC[channel] & $7F
elsif volC[channel]
volC[channel]--
psgWrite(mbVIAs[channel], CENVAMP, volC[channel])
if volC[channel] == 0
noteC[channel] = 0
numNotes--
fin
fin
next
fin
until mbVIA1->IFR & $40 // Wait for T1 interrupt
mbVIA1->IFR = $40 // Clear interrupt
if yieldTime <= seqTime; func()#0; yieldTime = seqTime + yield; fin
until quit