mirror of
https://github.com/dschmenk/PLASMA.git
synced 2025-01-22 08:31:36 +00:00
Music sequencer as a module
This commit is contained in:
parent
165f16d048
commit
95e94ce028
@ -4,11 +4,31 @@ import sys
|
||||
#from mido import MidiFile
|
||||
import mido
|
||||
|
||||
mid = mido.MidiFile(sys.argv[1])
|
||||
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'
|
||||
sys.exit(0)
|
||||
# Parse optional arguments
|
||||
while optarg < (len(sys.argv) - 1):
|
||||
if sys.argv[optarg] == '-t': # Override tempo percentage
|
||||
timescale = float(sys.argv[optarg + 1]) * 16.0
|
||||
optarg += 2
|
||||
if sys.argv[optarg] == '-p': # Add extra percussion channel
|
||||
extperchan = int(sys.argv[optarg + 1]) - 1
|
||||
optarg += 2
|
||||
mid = mido.MidiFile(sys.argv[optarg])
|
||||
timeshift = timescale
|
||||
totaltime = 0
|
||||
eventtime = 0.0
|
||||
for msg in mid:
|
||||
eventtime += msg.time * timeshift
|
||||
#print '; time = ', msg.time
|
||||
if msg.type == 'note_on' or msg.type == 'note_off':
|
||||
deltatime = int(msg.time * 16 + 0.5)
|
||||
#if eventtime > 0.0 and eventtime < 0.5:
|
||||
# eventtime = 0.5
|
||||
deltatime = int(round(eventtime))
|
||||
octave = int(msg.note / 12 - 1)
|
||||
onote = int(msg.note % 12)
|
||||
lrchan = int(msg.channel & 1)
|
||||
@ -20,15 +40,29 @@ for msg in mid:
|
||||
if octave < 0:
|
||||
octave = 0
|
||||
totaltime += deltatime
|
||||
if msg.channel == 9:
|
||||
if msg.channel == 9 or msg.channel == extperchan:
|
||||
#
|
||||
# Percussion
|
||||
#
|
||||
if vol > 0 and deltatime > 0:
|
||||
print '\t!BYTE\t${0:02X}, ${1:02X}, ${2:02X}'.format(deltatime, msg.note >> 3, 2)
|
||||
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)
|
||||
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)
|
||||
eventtime = 0.0
|
||||
else:
|
||||
#
|
||||
# Note
|
||||
#
|
||||
print '\t!BYTE\t${0:02X}, ${1:02X}, ${2:02X}'.format(deltatime, 0x80 | (octave << 4) | onote, (lrchan << 7) | vol)
|
||||
print '\t!BYTE\t$00, $00, $00'
|
||||
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
|
||||
#timeshift = msg.tempo / 500000.0 * timescale
|
||||
#print '; timescale = ', timescale
|
||||
elif msg.type == 'time_signature':
|
||||
pass
|
||||
elif msg.type == 'control_chage':
|
||||
pass
|
||||
elif msg.type == 'program_change':
|
||||
pass
|
||||
print '\t!BYTE\t${0:02X}, $00, $00'.format(int(eventtime + 0.5))
|
||||
|
@ -2,7 +2,7 @@
|
||||
AFLAGS = -o $@
|
||||
MBTEST = mbtest.bin
|
||||
SPKRTEST = spkrtest.bin
|
||||
SEQPLAY = seqplay.bin
|
||||
SEQPLAY = seqplay.rel
|
||||
PLASM = ../plasm
|
||||
#
|
||||
# Image filetypes for Virtual ][
|
||||
@ -33,6 +33,7 @@ $(SPKRTEST): test.seq spkrtest.pla spkrvm.s $(PLASM)
|
||||
$(PLASM) -AO < spkrtest.pla > spkrtest.a
|
||||
acme -o $(SPKRTEST) spkrvm.s
|
||||
|
||||
$(SEQPLAY): seqplay.pla seqvm.s $(PLASM)
|
||||
$(PLASM) -AO < seqplay.pla > seqplay.a
|
||||
acme -o $(SEQPLAY) seqvm.s
|
||||
$(SEQPLAY): seqplay.pla test.seq $(PLASM)
|
||||
$(PLASM) -AOM < seqplay.pla > seqplay.a
|
||||
acme --setpc 4094 -o $(SEQPLAY) seqplay.a
|
||||
acme --setpc 1024 -o testseq.bin test.seq
|
||||
|
@ -398,12 +398,12 @@ def mbSequence(track)
|
||||
mbVIA1=>T1L = $F9C2 // 16 Ints/sec
|
||||
mbVIA1=>T1C = $F9C2 // 16 Ints/sec
|
||||
mbVIA1->IFR = $40 // Clear interrupt
|
||||
// mbVIA1->IER = $C0 // Enable Timer1 interrupt
|
||||
mbVIA1->IER = $C0 // Enable Timer1 interrupt
|
||||
//enableInts
|
||||
repeat
|
||||
//puts("seqTime = "); puti(seqTime); puts(" eventTime = "); puti(eventTime); putln
|
||||
while eventTime == seqTime
|
||||
note = seqEvent->percnote
|
||||
note = seqEvent->percnote
|
||||
if note & $80
|
||||
//
|
||||
// Note event
|
||||
@ -460,17 +460,18 @@ def mbSequence(track)
|
||||
//
|
||||
period = seqEvent->perchanvol
|
||||
if period
|
||||
psgWrite(mbVIA1, MIXER, $1C) // NG on C, Tone on B, A
|
||||
psgWrite(mbVIA1, CENVAMP, $10)
|
||||
psgWrite(mbVIA1, NGFREQ, note)
|
||||
psgWrite(mbVIA1, ENVPERIOD+1, period)
|
||||
psgWrite(mbVIA1, ENVSHAPE, $00) // Single decay
|
||||
if mbVIA2
|
||||
if (period & $80)
|
||||
psgWrite(mbVIA1, MIXER, $1C) // NG on C, Tone on B, A
|
||||
psgWrite(mbVIA1, CENVAMP, $10)
|
||||
psgWrite(mbVIA1, ENVSHAPE, (note >> 4) & $04)
|
||||
psgWrite(mbVIA1, NGFREQ, (note >> 1) & $1F)
|
||||
psgWrite(mbVIA1, ENVPERIOD+1, period & $7F)
|
||||
elsif mbVIA2
|
||||
psgWrite(mbVIA2, MIXER, $1C) // NG on C, Tone on B, A
|
||||
psgWrite(mbVIA2, CENVAMP, $10)
|
||||
psgWrite(mbVIA2, NGFREQ, note)
|
||||
psgWrite(mbVIA2, ENVSHAPE, (note >> 4) & $04)
|
||||
psgWrite(mbVIA2, NGFREQ, (note >> 1) & $1F)
|
||||
psgWrite(mbVIA2, ENVPERIOD+1, period)
|
||||
psgWrite(mbVIA2, ENVSHAPE, $00) // Single decay
|
||||
fin
|
||||
else
|
||||
eventTime = -1 // Exit out
|
||||
@ -571,7 +572,9 @@ def mbSequence(track)
|
||||
end
|
||||
heap = *freemem
|
||||
if mbSearch
|
||||
mbSequence(@toneTrack)
|
||||
repeat
|
||||
mbSequence(@toneTrack)
|
||||
until ^$C000 > 127
|
||||
^$C010
|
||||
fin
|
||||
done
|
||||
|
@ -1,3 +1,6 @@
|
||||
include "../inc/cmdsys.plh"
|
||||
include "../inc/fileio.plh"
|
||||
include "../inc/args.plh"
|
||||
//
|
||||
// Usage is documented following the source in this file...
|
||||
//
|
||||
@ -88,14 +91,10 @@ word[5] arpeggioDuration = DUR16TH, DUR16TH, DUR16TH/2, DUR16TH/3, DUR16TH/4
|
||||
//
|
||||
// These are utility sequences/routines needed to test the music sequencer code.
|
||||
//
|
||||
asm toneTrack
|
||||
include "ultima3.seq"
|
||||
end
|
||||
asm putc(ch)#0
|
||||
LDA ESTKL,X
|
||||
INX
|
||||
ORA #$80
|
||||
JMP $FDED
|
||||
word arg
|
||||
word ref
|
||||
asm defs
|
||||
!SOURCE "../vmsrc/plvmzp.inc"
|
||||
end
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
@ -477,17 +476,18 @@ def mbSequence(yield, func)#0
|
||||
//
|
||||
period = seqEvent->perchanvol
|
||||
if period
|
||||
psgWrite(mbVIA1, MIXER, $1C) // NG on C, Tone on B, A
|
||||
psgWrite(mbVIA1, CENVAMP, $10)
|
||||
psgWrite(mbVIA1, NGFREQ, note)
|
||||
psgWrite(mbVIA1, ENVPERIOD+1, period)
|
||||
psgWrite(mbVIA1, ENVSHAPE, $00) // Single decay
|
||||
if mbVIA2
|
||||
if (period & $80)
|
||||
psgWrite(mbVIA1, MIXER, $1C) // NG on C, Tone on B, A
|
||||
psgWrite(mbVIA1, CENVAMP, $10)
|
||||
psgWrite(mbVIA1, ENVSHAPE, (note >> 4) & $04)
|
||||
psgWrite(mbVIA1, NGFREQ, (note >> 1) & $1F)
|
||||
psgWrite(mbVIA1, ENVPERIOD+1, period & $7F)
|
||||
elsif mbVIA2
|
||||
psgWrite(mbVIA2, MIXER, $1C) // NG on C, Tone on B, A
|
||||
psgWrite(mbVIA2, CENVAMP, $10)
|
||||
psgWrite(mbVIA2, NGFREQ, note)
|
||||
psgWrite(mbVIA2, ENVSHAPE, (note >> 4) & $04)
|
||||
psgWrite(mbVIA2, NGFREQ, (note >> 1) & $1F)
|
||||
psgWrite(mbVIA2, ENVPERIOD+1, period)
|
||||
psgWrite(mbVIA2, ENVSHAPE, $00) // Single decay
|
||||
fin
|
||||
else
|
||||
if seqRepeat
|
||||
@ -662,7 +662,7 @@ def spkrSequence(yield, func)#0
|
||||
// Percussion event
|
||||
//
|
||||
if seqEvent->perchanvol
|
||||
spkrPWM($D000, 0, 64) // Play some random sample as percussion
|
||||
//spkrPWM($D000, 0, 64) // Play some random sample as percussion
|
||||
else
|
||||
if seqRepeat
|
||||
musicPlay(seqTrack, TRUE)
|
||||
@ -774,7 +774,7 @@ end
|
||||
//
|
||||
// Get a keystroke and convert it to upper case
|
||||
//
|
||||
export def getUpperKey#1
|
||||
export def getKey#1
|
||||
byte key
|
||||
|
||||
while ^$C000 < 128
|
||||
@ -782,58 +782,31 @@ export def getUpperKey#1
|
||||
loop
|
||||
key = ^$C000 & $7F
|
||||
^$C010
|
||||
if key >= 'a' and key <= 'z'
|
||||
key = key - $20
|
||||
fin
|
||||
return key
|
||||
end
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// More utility routines to test the getUpperKey routine
|
||||
//
|
||||
def putln#0
|
||||
putc($0D)
|
||||
end
|
||||
def puts(str)#0
|
||||
byte i
|
||||
|
||||
for i = 1 to ^str
|
||||
putc(^(str+i))
|
||||
next
|
||||
end
|
||||
// More utility routines to test the getKey routine
|
||||
//
|
||||
// Sample background process
|
||||
//
|
||||
def backgroundProc#0
|
||||
^$0400++
|
||||
end
|
||||
//
|
||||
// Test functionality
|
||||
//
|
||||
def test#0
|
||||
byte key
|
||||
|
||||
puts("Press <RETURN> to exit:")
|
||||
while TRUE
|
||||
key = getUpperKey
|
||||
when key
|
||||
is $0D
|
||||
return
|
||||
is 'P'
|
||||
musicPlay(@toneTrack, TRUE)
|
||||
break
|
||||
is 'S'
|
||||
musicStop
|
||||
break
|
||||
otherwise
|
||||
putc(key)
|
||||
wend
|
||||
loop
|
||||
end
|
||||
|
||||
musicPlay(@toneTrack, TRUE)
|
||||
test
|
||||
musicStop
|
||||
arg = argNext(argFirst)
|
||||
if ^arg
|
||||
ref = open(arg, sysbuf)
|
||||
if ref
|
||||
read(ref, heapmark(), heapavail())
|
||||
close(ref)
|
||||
musicPlay(heapmark(), TRUE)
|
||||
getKey
|
||||
musicStop
|
||||
else
|
||||
puts("File not found.\n")
|
||||
fin
|
||||
fin
|
||||
done
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
@ -844,14 +817,20 @@ musicPlay(trackPtr, trackRepeat)
|
||||
Params:
|
||||
Pointer to a track sequence created from the cvtmidi.py tool
|
||||
Repeat flag - TRUE or FALSE.
|
||||
The first time its is called, it will try and search for a MockingBoard.
|
||||
However, it is noted that this can cause problems if a Z-80 card is installed.
|
||||
The scanning routine might cause a hang if it encounters a Z-80 card before
|
||||
it finds a MockingBoard. In order to make this robust, it might be best to
|
||||
prompt the user to search for the MockingBoard, enter the actual MockingBoard
|
||||
slot, or skip the MockingBoard and use the internal speaker.
|
||||
|
||||
musicStop()
|
||||
Stop playing a track sequence in the getUpperKey routine
|
||||
The getUpperKey routine will call a dummy sequence routine that will
|
||||
keep the correct timing for any background processing
|
||||
|
||||
getUpperKey()
|
||||
Wait for a keypress and return the upper case character
|
||||
getKey()
|
||||
Wait for a keypress and return the character
|
||||
While waiting for the keypress, the track sequence will be played though
|
||||
either the MockingBoard (if present) or the internal speaker. Optionally,
|
||||
a background function can be called periodically based on the sequencer
|
||||
@ -872,3 +851,20 @@ spkrPWM(samples, speed, len)
|
||||
Pointer to 8 bit pulse width samples
|
||||
Speed to play through samples
|
||||
Length of sample
|
||||
|
||||
The main routines for sequencing music are:
|
||||
|
||||
mbSequence(yield, func)
|
||||
spkrSequence(yield, func)
|
||||
noSequence(yield, func)
|
||||
|
||||
All three try and provide more functionality than would be present in
|
||||
previous music sequencers. The MockingBoard sequencer will attempt to play up
|
||||
to 9 tones per sound generator (18 if a MockingBoard II is found). Up to
|
||||
four notes will be played simultaneously on the internal speaker. In order
|
||||
to play more notes than the hardware normally supports, a technique using
|
||||
arpeggio (playing multiple notes in a quick sequence rather than concurrently)
|
||||
pulls off this feat. The sequencers will immediately return if a keypress is
|
||||
detected. Finally, during the sequencing, a background function can be periodically
|
||||
called every 'yield' time which has a resolution of a 16th of a second. Pass
|
||||
in zero for 'yield' and/or 'func' to disable any background calls.
|
||||
|
@ -56,6 +56,7 @@ byte hpmarkstr = "HEAPMARK"
|
||||
byte hpalignstr = "HEAPALLOCALIGN"
|
||||
byte hpallocstr = "HEAPALLOC"
|
||||
byte hprelstr = "HEAPRELEASE"
|
||||
byte hpavlstr = "HEAPAVAIL"
|
||||
byte memsetstr = "MEMSET"
|
||||
byte memcpystr = "MEMCPY"
|
||||
byte uisgtstr = "ISUGT"
|
||||
@ -68,30 +69,31 @@ byte modadrstr = "MODADDR"
|
||||
byte argstr = "ARGS"
|
||||
byte autorun = "AUTORUN"
|
||||
byte prefix[] // overlay with exported symbols table
|
||||
word exports = @sysstr, @syscall
|
||||
word = @callstr, @call
|
||||
word = @putcstr, @cout
|
||||
word = @putlnstr, @crout
|
||||
word = @putsstr, @prstr
|
||||
word = @getcstr, @cin
|
||||
word = @getsstr, @rdstr
|
||||
word = @hpmarkstr, @markheap
|
||||
word = @hpallocstr,@allocheap
|
||||
word = @hpalignstr,@allocalignheap
|
||||
word = @hprelstr, @releaseheap
|
||||
word = @memsetstr, @memset
|
||||
word = @memcpystr, @memcpy
|
||||
word = @uisgtstr, @uword_isgt
|
||||
word = @uisgestr, @uword_isge
|
||||
word = @uisltstr, @uword_islt
|
||||
word = @uislestr, @uword_isle
|
||||
word = @loadstr, @loadmod
|
||||
word = @execstr, @execmod
|
||||
word = @modadrstr, @lookupstrmod
|
||||
word = @machidstr, MACHID
|
||||
word = @argstr, @cmdln
|
||||
word = 0
|
||||
word syslibsym = @exports
|
||||
word exports = @sysstr, @syscall
|
||||
word = @callstr, @call
|
||||
word = @putcstr, @cout
|
||||
word = @putlnstr, @crout
|
||||
word = @putsstr, @prstr
|
||||
word = @getcstr, @cin
|
||||
word = @getsstr, @rdstr
|
||||
word = @hpmarkstr, @markheap
|
||||
word = @hpallocstr,@allocheap
|
||||
word = @hpalignstr,@allocalignheap
|
||||
word = @hprelstr, @releaseheap
|
||||
word = @hpavlstr, @availheap
|
||||
word = @memsetstr, @memset
|
||||
word = @memcpystr, @memcpy
|
||||
word = @uisgtstr, @uword_isgt
|
||||
word = @uisgestr, @uword_isge
|
||||
word = @uisltstr, @uword_islt
|
||||
word = @uislestr, @uword_isle
|
||||
word = @loadstr, @loadmod
|
||||
word = @execstr, @execmod
|
||||
word = @modadrstr, @lookupstrmod
|
||||
word = @machidstr, MACHID
|
||||
word = @argstr, @cmdln
|
||||
word = 0
|
||||
word syslibsym = @exports
|
||||
//
|
||||
// Utility functions
|
||||
//
|
||||
|
Loading…
x
Reference in New Issue
Block a user