From faa5270a0c9f18e195be079cbbadbc8107d5849f Mon Sep 17 00:00:00 2001 From: Martin Haye Date: Mon, 26 Mar 2018 13:48:10 -0700 Subject: [PATCH] Improved song conversion and playback. --- Platform/Apple/tools/ConvertMidi/cvtmid.py | 5 +- .../src/org/badvision/A2PackPartitions.groovy | 129 +++++++++++------- Platform/Apple/virtual/src/plasma/sndseq.pla | 49 ++++++- 3 files changed, 128 insertions(+), 55 deletions(-) diff --git a/Platform/Apple/tools/ConvertMidi/cvtmid.py b/Platform/Apple/tools/ConvertMidi/cvtmid.py index c04b9111..4c7eabad 100644 --- a/Platform/Apple/tools/ConvertMidi/cvtmid.py +++ b/Platform/Apple/tools/ConvertMidi/cvtmid.py @@ -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 diff --git a/Platform/Apple/tools/PackPartitions/src/org/badvision/A2PackPartitions.groovy b/Platform/Apple/tools/PackPartitions/src/org/badvision/A2PackPartitions.groovy index 1a1cb751..4bd261aa 100644 --- a/Platform/Apple/tools/PackPartitions/src/org/badvision/A2PackPartitions.groovy +++ b/Platform/Apple/tools/PackPartitions/src/org/badvision/A2PackPartitions.groovy @@ -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", "", "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) diff --git a/Platform/Apple/virtual/src/plasma/sndseq.pla b/Platform/Apple/virtual/src/plasma/sndseq.pla index b29f23d9..9472f12e 100755 --- a/Platform/Apple/virtual/src/plasma/sndseq.pla +++ b/Platform/Apple/virtual/src/plasma/sndseq.pla @@ -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