Well first of all, we need a way to grab and format messages.
In [1]:
%cd -q '..'
import mido
import copy
from commons import mido_util
In [2]:
from commons import timers
We want a way to grab the messages with the appropriate time, put them in a list, also print them out as they arrive, except for the clock ones, which we don't want because they would spam the whole thing. Also maybe include a nice signal to terminate, such as the foot pedal or something.
In [3]:
from commons.messages import controlstate, controls
In [4]:
def collector(state=None):
if state is None:
newstate = controlstate.MidiControlState()
else:
newstate = copy.deepcopy(state)
collection_list = []
with mido.open_input("DGX-505 MIDI 1") as inport:
timer = timers.offsetTimer()
for msg in inport:
msg.time = timer()
wrapped = newstate.feed_message(msg)
collection_list.append(wrapped)
if msg.type != "clock":
print(wrapped)
if wrapped.wrap_type == controls.Control.PEDAL:
break
return newstate, collection_list
In [5]:
def writeout(outlist, outname):
with open(outname, 'w') as outfile:
for wrapped in outlist:
outfile.write(str(wrapped.message)+'\n')
Now let's try it with the initial send after power on.
In [16]:
s, c = collector()
In [17]:
len(c)
Out[17]:
In [18]:
writeout(c, 'documents/data/initial_send.txt')
In [19]:
s._dump()
Hmmm, what about if we have a song selected instead of a style?
In [22]:
s1, c1 = collector(s)
writeout(c1, 'documents/data/initial_send_song1.txt')
That's with song 001 selected.
How about a blank user song?
In [28]:
s2, c2 = collector(s1)
writeout(c2, 'documents/data/initial_send_user.txt')
Huh, it looks like it actually sends a pedal sustain for that. Let's use a note_on instead.
In [6]:
def collector(state=None):
if state is None:
newstate = controlstate.MidiControlState()
else:
newstate = copy.deepcopy(state)
collection_list = []
with mido.open_input("DGX-505 MIDI 1") as inport:
timer = timers.offsetTimer()
for msg in inport:
msg.time = timer()
wrapped = newstate.feed_message(msg)
collection_list.append(wrapped)
if msg.type != "clock":
print(wrapped)
if msg.type == "note_on":
break
return newstate, collection_list
In [30]:
s3, c2 = collector(s2)
writeout(c2, 'documents/data/initial_send_user.txt')
In [31]:
s3._dump()
With a blank user song, it looks like there are more of the settings set (to defaults?)
How about if we have an initial send, but with the song out OFF?
In [32]:
s4, c4 = collector(s3)
writeout(c4, 'documents/data/initial_send_songoff.txt')
And how about song and style out OFF?
In [33]:
s5, c5 = collector(s4)
writeout(c5, 'documents/data/initial_send_stylesongoff.txt')
I guess with voice out OFF it wouldn't send that either
In [34]:
s6, c6 = collector(s5)
writeout(c6, 'documents/data/initial_send_alloff.txt')
Whoops on that one, I needed to turn keyboard OUT back on to send the note. Ah well.
Now for some QUESTIONS.
Sending messages doesn't change the internal instrument thing, so it shouldn't affect the initial send thing at all.
In [35]:
s6
Out[35]:
In [40]:
s6._channels[0].control_value(controls.Control.VOLUME)
Out[40]:
In [41]:
with mido.open_output('DGX-505 MIDI 1') as outport:
for i in range(16):
outport.send(controls.cc(controls.Control.VOLUME, channel=i, value=1))
In [42]:
s7, c7 = collector(s6)
Yep, that does nothing.
... wait, have i got the chorus type wrong this whole time?
In [43]:
s8, c8 = collector(s7)
Okay, i've stuffed something up here.
In [46]:
cc8 = [x for x in c8 if x.message.type != "clock"]
In [47]:
cc8
Out[47]:
In [60]:
" ".join(format(x, "02X") for x in (cc8[1].message.data))
Out[60]:
In [72]:
%load_ext autoreload
%autoreload
In [4]:
from commons.messages import wrappers
from commons import enums
In [81]:
wrappers.wrap(cc8[1].message)
Out[81]:
Ok, so that's fixed.
I've noticed that the GM SYSTEM ON resets some things on the panel. Could it reset some other stuff as well? Here we have the reverb type and chorus type set to weird things, and for good measure let's also change the pitch bend range and the panel sustain and other stuff:
In [84]:
ns, nsc = collector(s8)
Now we do GM_SYSTEM ON
In [85]:
with mido.open_output('DGX-505 MIDI 1') as outport:
outport.send(controls.gm_on())
In [86]:
rs, rsc = collector(ns)
In [91]:
def nonclock(l):
return (x for x in l if x.message.type != 'clock')
for n, r in zip(nonclock(nsc), nonclock(rsc)):
if str(n) != str(r):
print(n, r)
It looks like the GM_ON reset only affects the reverb and chorus panel settings. nice to know.
In [95]:
with mido.open_output('DGX-505 MIDI 1') as outport:
for i in range(16):
outport.send(controls.cc(controls.Control.VOLUME, channel=i, value=1))
In [96]:
with mido.open_output('DGX-505 MIDI 1') as outport:
for i in range(16):
outport.send(controls.cc(controls.Control.RESET_CONTROLS, channel=i, value=1))
In [97]:
with mido.open_output('DGX-505 MIDI 1') as outport:
for i in range(16):
outport.send(controls.cc(controls.Control.VOLUME, channel=i, value=127))
Huh, it looks like Reset Controllers does nothing to volume either.
In [98]:
with mido.open_output('DGX-505 MIDI 1') as outport:
for i in range(16):
outport.send(controls.cc(controls.Control.MODULATION, channel=i, value=127))
In [99]:
with mido.open_output('DGX-505 MIDI 1') as outport:
for i in range(16):
outport.send(controls.cc(controls.Control.RESET_CONTROLS, channel=i, value=1))
But it works with Modulation for some reason. hmmmmmm.
In [101]:
outport = mido.open_output('DGX-505 MIDI 1')
In [103]:
outport.send(controls.gm_on())
outport.send(controls.local(False))
Let's start from the top: voice volume.
In [104]:
cc = controls.cc
C = controls.Control
In [105]:
outport.send(cc(C.VOLUME, 0))
No sound.
In [107]:
outport.send(cc(C.RESET_CONTROLS, 64))
Still no sound.
In [108]:
outport.send(cc(C.VOLUME, 64))
In [122]:
outport.send(controls.gm_on())
In [113]:
outport.send(cc(C.VOLUME, 110))
We can conclude that RESET_CONTROLS does nothing to voice volume, and I have no idea what GM_ON resets it to.
In [118]:
outport.send(cc(C.PAN, 0))
All sound on the left.
In [120]:
outport.send(cc(C.RESET_CONTROLS, 42))
All sound still on the left.
Just checking if i'm not missing anything:
In [121]:
for i in range(128):
outport.send(cc(C.RESET_CONTROLS, i))
Nope, the reset doesn't work for pan either.
In [123]:
outport.send(controls.gm_on())
outport.send(cc(C.REVERB, 127))
In [124]:
outport.send(cc(C.RESET_CONTROLS, 42))
Still reverby.
In [127]:
outport.send(controls.gm_on())
outport.send(cc(C.CHORUS, 127))
In [128]:
outport.send(cc(C.RESET_CONTROLS, 3))
Neither does the chorus
In [131]:
outport.send(controls.gm_on())
outport.send(cc(C.PEDAL, 127))
Pedal
In [132]:
outport.send(cc(C.RESET_CONTROLS, 3))
Hey, it does work for pedal! Turns it OFF.
In [133]:
outport.send(cc(C.RELEASE, 127))
In [134]:
outport.send(cc(C.RESET_CONTROLS, 3))
Also Panel Sustain, that is, release time
In [135]:
outport.send(cc(C.RELEASE, 0))
In [136]:
outport.send(cc(C.RESET_CONTROLS, 3))
In [137]:
outport.send(controls.gm_on())
I think the default is 0x40, probably.
In [138]:
outport.send(cc(C.MODULATION, 127))
In [144]:
outport.send(cc(C.RESET_CONTROLS, 3))
In [143]:
outport.send(cc(C.MODULATION, 0))
The modulation wheel, I guess, is reset to zero.
In [154]:
outport.send(cc(C.EXPRESSION, 127))
In [153]:
outport.send(cc(C.EXPRESSION, 64))
In [155]:
outport.send(cc(C.RESET_CONTROLS, 3))
Expression can be reset as well. Dunno what the default is.
In [159]:
outport.send(cc(C.PORTAMENTO_CTRL, 12))
I don't even know what portamento even does.
In [160]:
outport.send(cc(0x05, 127))
I've been reading through the MIDI Specs. Things to note:
The specification for XG 2.0 can be found through http://www.jososoft.dk/yamaha/docs_specs.htm dated April 2001. XGLite is a subset of this specification.
I'm going to assume that the implementation of XGLite is like XG, only with a whole lot fewer controls and voices available, so no super fancy stuff. Nevertheless, we do have our 16 channel multi timbral tone generator (with 32 note polyphony).
There are melody voices and there are rhythym voices (drum kits).
The specification says:
If the tone generator does not have a drum kit corresponding to the specified program number, it will ignore the message and continue to use the current drum kit.
If Program change makes a change from normal voice to drum kit, the part mode shall revert to what it was before normal mode was invoked. Upon receipt of XG System ON, the system shall virtually set this "revert-to" mode to Drum Setup 2 for all Parts 1 to 16, except for Part 10.
Bank MSB:
MSB | |
---|---|
00 |
Melody voice |
01 –3F |
Model-exclusive area |
40 |
SFX voice |
41 –77 |
Reserved for XG extensions |
78 |
GM L2 Rhythm Kit |
79 |
GM L2 Melody voice |
7A –7D |
Reserved for XG extensions |
7E |
SFX kit |
7F |
Rhythm kit |
Bank LSB is an extension area.
If support is included for one or more voices in an extension bank, then all the other program change numbers in that bank are filled with the corresponding voices of Bank 0 (basic voices)
Note 1: By default, channel 10 plays rhythm voices while other channels use bank 0 melody voices (as in GM Level 1)
Note 2: If the new Bank Select MSB is
00
(melody voice) but the tone generator does not support the melody voice corresponding to the last recieved Bank Select LSB, the channel reverts to the Bank Select LSB corresponding to its most recently played melody voice.Note 3: If the new Bank Select MSB is
7F
(rhythm voice) the tone generator unconditionally uses LSB00
without using the most recently received Bank Select LSB. If the tone generator does not support a drum kit corresponding to the channel's most recently received Program Change, the channel will revert to the Program Change corresponding to its most recently played rhythm kit.Note 4: If a Bank Select MSB value of
01
–77
or7A
–7E
(model-exclusive area, SFX voice, or XG extension voice) is received and the tone generator does not have a voice corresponding to the last received LSB and Program change, the tone generator shall produce no sound for that channel regardless of subsequent Key On messages.In the case where a melody voice is being changed first to a voice in bank-LSB A and then to a voice in bank-LSB B, and the change to A is possible but the change to B is not possible, A will be used as the substitute for B. If neither the change to A nor the change to B is possible, the voice of the previous bank will [be] substituted for A and B.
In the case where a rhythm voice is being changed first program number kit A and then to program number kit B, and the change to A is possible but the change to B is not possible, A will be used as the substitute for B. If neither the change to A nor the change to B is possible, the voice of the previous bank will [be] substituted for A and B.
Control 01
, defaults to 00
By default, this message controls vibrato depth only
Has no effect on portamento control. Turns out those things aren't the same thing? Is this a difference in the MIDI Spec?
06
and 26
, as usual
Control 07
, default 64
In the DGX-505 chart this is called "Part Volume".
Control 0A
, default 40
Apparently 00
and 01
are the same (L63)... it goes from there up as you would expect to 7F
being R63.
Also, that's what Pan stands for?
Control 0B
. default 7F
Control 40
. OFF/ON. default 00
(OFF).
This setting also affects the release part of the sound following note-off (after-damper effect).
Not supported by 505.
Controls 47
, 48
, 49
, 4A
respectively
Range 00
:-64, 40
:0, 7F
:+63; default 40
(0)
On some voices, the effective range is narrower than the range which can be set.
Harmonic content adjusts resonance, release/attack adjusts the envelope, brightness adjusts the filter cutoff frequency. Controls are relative to the default 40
.
Control 54
.
Portamento time is always 0.
... So, what, it just does the portamento instantly or something?
Control 5B
. Default 28
Control 5D
. Default 00
Not supported by 505
Controls 60
, 61
.
The data byte is ignored.
Not supported by 505. This is full of complex stuff for the full XG I suppose.
LSB on 64
, MSB on 65
. Default: 7F 7F
.
The order here is LSB MSB, but the instrument transmits in MSB LSB order.
MSB LSB: 00 00
. Data on MSB, default 02
. LSB ignored.
MSB LSB: 00 01
, 00 02
respectively.
Data on MSB, with range 00
:-64, 40
:0, 7F
:+63.
Default 40
.
Looks like LSB is probably ignored here too.
Control 78
, with data 00
.
Silences all notes on the channel, but does not reset stuff that was set by channel messages
Control 79
, with data 00
.
Resets to default:
It also sets off reception of Portamento Control. It does not reset the Portamento source key.
Huh, so how does the portamento control work? Must consult the MIDI spec?
Control 7B
, data 00
Turns all notes that are ON, OFF. Doesn't stop playing if in sustain or sostenuto phase until those phases become off as well.
Not particularly relevant, as instrument only supports mode 3.
Thus, these just act as notes/sound off messages (depending on type: 7C
, 7D
as Notes Off and 7E
, 7F
as Sound Off.
Interestingly according to the spec OMNI ON does not implement OMNI ON.
Apparently in XG, special parameters can be set with
F0 43 1n 4C aa aa aa dd .. dd F7
.
n
is the device number, 4C
the model ID, aa aa aa
the address hi/mid/lo, dd .. dd
the data.
A single message shall be used to set the data value, even if the value's data size consists of multiple bytes. The tone generator shall not accept the message if the number of sent data bytes is less than the required number of data bytes. It shall not be possible, when sending data for a multibyte parameter (such as master tuning), to send the high-order byte only.
Note that during the Initial Send, a sysEx message is recieved:
F0 43 10 4C 00 00 7E 00 F7
So now we have some idea of what this is:
Device 0
, set parameter address 00 00 7E
to value 00
.
Sets parameters as a block
F0 43 0n 4C bb bb aa aa aa dd .. dd cc F7
Here, bb bb
are Byte Count MSB/LSB, which corresponds to the "TOTAL SIZE" figures in Attached Chart 5 of the spec.
cc
is a checksum, such that low-order 7 bits of sum of byte count, address, data, checksum itself = 0.
If sending consecutive bulk dumps, leave an interval of about 10ms between the F7 and the next F0.
Optional: has no meaning on tone generators without MIDI OUT
Gets the value of a specified parameter
F0 43 4n 4C aa aa aa F7
Optional, as above
F0 43 2n 4C aa aa aa F7
There are ten zillion of these, which probably aren't implemented in XGLite.
Address 00 00 00
, data aa bb cc dd
, default 00 04 00 00
Master tuning as aa
×1000
+ bb
×0100
+ cc
×0010
+ dd
×0001
- 0400
in units of 0.1 cents.
Note: This is not the "MIDI Master Tuning" SysEx message
F0 43 1n 27 30 00 00 mm ll cc F7
Address 00 00 04
, data dd
, default 7F
.
This parameter value can also be changed by the Universal SysEx Master Volume message
F0 7F 7F 04 01 ll mm F7
,
which is documented in the DGX-505 manual (ll
ignored)
Address 00 00 05
, data dd
, default 00
. Optional.
Address 00 00 06
, data dd
, default 40
, range 28
–58
(-24 to +24)
Address 00 00 7D
, data dd
, default --, range 00
–(Drum setup count - 1). Initialises drum setup parameters, but doesn't change kit number.
Address 00 00 7E
, data dd
. If data is 00
, resets tone generator to XG initial state.
Aha, now this is what our message F0 43 10 4C 00 00 7E 00 F7
is.
Address 00 00 7F
, data dd
, If data is 00
, resets tone generator mode to factory defaults
These are for the Reverb and Chorus (and Variation, but not supported?) Types. Effect type is set by MSB and LSB.
For unsupported MSB, No Effect is used, which is 0.
For unsupported LSB, older models change non-supported LSBs to 0, while newer models calculate
Substitute LSB = Integer part of (given LSB/32) × 32
ending up with 0, 32, 64, or 96. Considering that the DGX-505 does not support any LSB greater than 32, I think having it be 0 is kind of the same thing.
Optional, for the purpose of compatibility with pre-XG models. Just the one message:
F0 43 1n 27 30 00 00 0m 0l cc F7
The DGX-505 implements this one.
F0 7E 7F 09 01 F7
Resets all settings besides MIDI Master Tuning to default. Acts on MIDI Master Tuning "the same" as XG System ON.
The DGX-505 implements this one.
F0 7F 7F 04 01 ll mm F7
The DGX-505 implements this one, ignoring ll
.
XG has 3 ways of changing master tuning:
2 and 3 are the same thing, of which latest setting is effective. XG System ON / GM System ON would return the tuning to the most recent 2 or 3 value, while All Parameter Rest / Factory Set would reset everything including 2 and 3.
These are documents from the official MIDI specifications, available at https://www.midi.org/specifications (a signup required). There's a whole bunch of documents, which include the complete MIDI 1.0 Specification (v4.2, Februrary 1996) and a whole bunch of recommended practices (RPs) and assorted updates since then.
Interesting, but apparently (from some Googling) not supported by USB-MIDI and quite possibly undone by the operating system libraries. So not particularly relevant to us, outside of clearing up some of the examples in the specs themselves. Mido doesn't seem to support parsing it.
Note On with velocity 0 is equivalent to Note Off (and useful for Running Status), but a separate Note Off is also provided with its own velocity for devices to use if they want. The DGX505 says it doesn't recognise Note Off velocity (it recognises Note Off messages, as a good implementer would).
Range | |
---|---|
00 –1F |
MSB for most continuous Controller Data |
20 –3F |
optional LSB for the controllers 00 –1F |
40 –45 |
Single byte, usually switches/pedals |
46 –4F |
Single byte, Sound control |
50 –5A |
Other single byte |
5B –5F |
Single byte, External effect depth |
60 –65 |
Inc/Dec, Parameters |
66 –77 |
Undefined, as of 1996 |
78
to 7F
are technically Channel Mode messages, not Control Change messages, but mido doesn't care.
The way MSB/LSB are supposed to work: LSB is optional. Once MSB is sent, then the LSB can be adjusted without sending another MSB message (as long as we don't need to change the MSB, obviously). When setting MSB, the LSB is implicitly set to zero.
Question: Does this apply to Bank MSB/LSB, which falls in the 00
–3F
range? How about Data Entry MSB/LSB?
The switches work on 00
–3F
as OFF, 40
–7F
as ON. An instrument does not technically have to interpret them as switches, and can interpret a non-switch as a switch, if it wants to. The spec recommends values of 00
and 7F
for OFF and ON.
10
–13
(with LSB 30
–33
), and 50
–53
(single byte) are the General Purpose Controllers, with manufacturer defined purpose. We don't use any of them.
MIDI transmitters must transmit MSB and LSB as a pair immediately followed by Program Change, as to not confuse if another message is inserted in a gap.
46
through 4A
. All except the first are implemented by DGX505. (see the XG section).
4B
through 4F
have no specified defaults (i.e. up to manufacturer, with no suggestion from the association).
54
. This is the complicated one, introduced for legato with portamento on POLY mode.
When a Note On is recieved after a Portamento Control, the voice pitch should glide from that specified in the Portamento Control (the pitch reference) to the new Note On, at a rate set by the Portamento Time Controller, ignoring Portamento ON/OFF (which I gather is for some other mode). It only affects the next Note-On on its channel, and does not affect any other notes that are sounding when it is recieved or overlapped (at least for Poly mode), with the exception of the reference note on that channel if it is already on. Note-Off must match the Note-On message, not the Portamento Controller message.
Okay, since the Portamento Time is set to 0 for this purpose in XG, I suppose this means that Portamento Control is only used for legato and not for slower sweeps.
RPN are agreed by the Association, NRPN are manufacturer specific. NRPN reception should be disabled by default on startup. When reception of RPN/NRPN is enabled, devices should wait until both MSB and LSB have been set.
Recievers should work with only LSB or MSB set messages, but transmitters should send both just in case each time it is changed.
RPN (MSB LSB) | parameter |
---|---|
00 00 |
Pitch Bend Sensitivity (MSB = semitones, LSB = cents) |
00 01 |
Fine Tuning |
00 02 |
Coarse Tuning |
00 03 |
Tuning Program Select |
00 04 |
Tuning Bank Select |
Additional RPNs have since been registered since 1996 as well, specified in the other documents.
The behaviour was vague in the 1996 spec, so a Recommended Practice document was issued (RP-018, Response to Data Inc/Dec Controllers, approved October 1997):
The value byte is ignored in Data Inc/Dec messages.
For RPN 0 and 1 (Pitch Bend Sensitivity and Fine Tuning), Inc/Dec should adjust the LSB primarily (changing the MSB on overflow). Because Pitch Bend Sensitivity works in cents, it overflows at 100 instead of 128.
For RPN 2, 3, 4, Inc/Dec works on the MSB.
For devices which don't support LSB for Pitch Bend Sensitivity / Fine Tuning, the number of Inc/Dec should be kept track of anyway so the overflow should be made at the right time (for example it should take 100 Incs to change 1 semitone, regardless of whether the sensitivity actually changes inbetween).
Not implemented on the DGX505. Mido has these as 'aftertouch' for the channel pressure and 'polytouch' for the polyphonic pressure.
These are the All Notes Off/ Sound Off / Omni / Poly as well as Reset All Controllers and Local Control. These are supposed to be recognised only when sent on the Basic Channel, and affect all the notes on the channels controlled by that Channel. I think the DGX-505 counts as 16 different instruments for this purpose, each their own Basic Channel??
This message should "reset the condition" of all continuous and switch controllers, pitch bend etc to an ideal initial state. This is a little vague in the 1996 spec, so a Recommended Practice was issued (RP-015, Response to Reset All Controllers, approved May 1999), which specifies what should be set default:
Control/setting | default |
---|---|
0B Expression |
7F |
01 Modulation |
00 |
40 –43 Pedals |
00 |
62 –65 (N)RPN |
7F (null) |
Pitch Bend | 00 40 (centre) |
Channel pressure | 0 |
Polyphonic pressure | 0 |
And what should not be set default:
07
Volume0A
Pan46
–4F
Sound controllers5B
–5F
Effect controllers78
–7F
Channel Mode messages00
&20
Bank SelectThis pretty much lines up with the XG spec.
General MIDI ON should perform this as well alongside anything else necessary (so I suppose it would also reset the things that wouldn't normally be reset).
Interrupts the "internal control path" from keyboard to sound generator.
Clock messages F8
are sent 24 times per quarternote; Start FA
and Stop FC
are self explanatory. FB
is the Continue command, which is used in conjuction with the song position/select messages which aren't implemented by the DGX505.
Exclusive messages can apparently end with any Non-Real-Time Status Byte (with real-time messages inbetween), but F7
should be sent at the end of all SysEx messages. This is possibly unsupported or abstracted away by USB-MIDI.
FE
is sent at least every 300 milliseconds if there is no other data.
FF
resets everything to power-on. Not implemented by DGX505.
F0 <id> <device id> <sub id 1> <sub id 2> .. F7
<id>
is 7E
for Non-Real Time Universal SysEx, and 7F
for real time.
<device id>
is a byte that should specify the physical device in most cases (as opposed to a channel or virtual device), except for when you need to address the parts separately. 7F
is the "all-call" broadcast address.
<sub id>
parts are for specifying what type of Universal SysEx message it is.
There's a whole protocol for exchanging files and what not over universal system exclusive messages, with handshakes etc. From inspecting the messages exchanged while transferring MIDI files using Musicsoft Downloader, the DGX-505 does not use this (it uses some Yamaha specific protocol) but it might be similar. Something to investigate later, perhaps.
Interestingly, the way that this protocol "7-bit-izes" octets is different from the way that the DGX-505 uses for its bulk dump: the leading bits are transmitted before the rest instead of afterward. This avoids the need for padding at the end.
There's a whole thing for microtuning and sample banks and dumps.
There's also a message for turning General MIDI OFF, F0 7E <id> 09 02 F7
if the device supports it.
Now I've read through the spec, we still have some Questions:
Does the DGX-505 support any of the more advanced XG settings beyond those listed in the manual?
What's the deal with LSB when MSB is set for bank and data entry? Does adjusting the LSB use the MSB previously set or will it jump somehow to the last one used, even for a different RPN?
Does DGX-505 (and/or XG(Lite) in general) have the proper behaviour for Data Inc/Dec as specified in RPN-018?
What is up with the Basic Channel?
What, exactly, does Portamento Control do?
Let's do these in reverse order. First up, Portamento Control.
In [5]:
import time
In [78]:
outport = mido.open_output('DGX-505 MIDI 1')
In [79]:
# Let's use a square lead on channel 5, everyone's favourite channel
controls.multisend(outport, controls.set_voice_numbers(101, 5))
In [169]:
outport.send(mido.Message('note_on', channel=5, note=64))
time.sleep(0.5)
outport.send(mido.Message('note_on', channel=5, note=68))
time.sleep(0.5)
outport.send(mido.Message('note_off', channel=5, note=68))
time.sleep(0.5)
outport.send(mido.Message('note_off', channel=5, note=64))
time.sleep(0.5)
Both notes turn on, and both notes have to be turned off.
In [170]:
outport.send(controls.cc(controls.Control.PORTAMENTO_CTRL, channel=5, value=64))
outport.send(mido.Message('note_on', channel=5, note=68))
time.sleep(0.5)
outport.send(mido.Message('note_off', channel=5, note=68))
I hear no difference, but PTC should have one extra effect:
In [172]:
outport.send(mido.Message('note_on', channel=5, note=64))
time.sleep(0.5)
outport.send(controls.cc(controls.Control.PORTAMENTO_CTRL, channel=5, value=64))
outport.send(mido.Message('note_on', channel=5, note=68))
time.sleep(0.5)
outport.send(mido.Message('note_off', channel=5, note=68))
Now the first note switches off as soon as the second comes on, and doesn't need to be switched off separately.
In [264]:
outport.send(mido.Message('note_on', channel=5, note=64))
outport.send(mido.Message('note_on', channel=5, note=68))
outport.send(mido.Message('note_on', channel=5, note=71))
time.sleep(0.5)
outport.send(controls.cc(controls.Control.PORTAMENTO_CTRL, channel=5, value=68))
outport.send(mido.Message('note_on', channel=5, note=56))
time.sleep(0.5)
outport.send(controls.cc(controls.Control.PORTAMENTO_CTRL, channel=5, value=64))
outport.send(mido.Message('note_on', channel=5, note=56))
time.sleep(0.5)
outport.send(controls.cc(controls.Control.PORTAMENTO_CTRL, channel=5, value=71))
outport.send(mido.Message('note_on', channel=5, note=56))
time.sleep(0.5)
outport.send(controls.cc(controls.Control.SOUND_OFF_XPOLY, channel=5, value=0))
In [265]:
outport.send(mido.Message('note_on', channel=5, note=64, velocity=64))
time.sleep(0.5)
outport.send(controls.cc(controls.Control.PORTAMENTO_CTRL, channel=5, value=64))
outport.send(mido.Message('note_on', channel=5, note=68, velocity=1))
time.sleep(0.5)
outport.send(mido.Message('note_on', channel=5, note=68, velocity=0))
Okay, I think I have a grip on what Portamento Control does. Also, the channel mode messages act on each channel separately, it seems
In [7]:
def panic():
for i in range(16):
outport.send(controls.cc(controls.Control.SOUND_OFF, channel=i, value=0))
Now, let's try pitch bend.
In [376]:
def pb(iv=0.5):
outport.send(mido.Message('pitchwheel', channel=5, pitch=0))
outport.send(mido.Message('note_on', channel=5, note=64))
time.sleep(iv)
outport.send(mido.Message('pitchwheel', channel=5, pitch=+8191))
time.sleep(iv)
outport.send(mido.Message('note_off', channel=5, note=64))
time.sleep(iv)
Let's set RPN:
In [6]:
C = controls.Control
cc = controls.cc
In [330]:
outport.send(cc(C.RPN_MSB, channel=5, value=0))
outport.send(cc(C.RPN_LSB, channel=5, value=0))
and decrement:
In [341]:
outport.send(cc(C.DATA_DEC, channel=5, value=0))
In [342]:
pb()
Hmm, no change.
How about setting?
In [343]:
outport.send(cc(C.DATA_MSB, channel=5, value=12))
In [372]:
pb()
In [371]:
outport.send(cc(C.DATA_DEC, channel=5, value=0))
In [379]:
outport.send(cc(C.DATA_MSB, channel=5, value=0))
for i in range(12):
outport.send(cc(C.DATA_INC, channel=5, value=0))
pb(0.15)
That's definitely incrementing by semitones, there.
What if we change the RPN and back?
In [380]:
outport.send(cc(C.RPN_LSB, channel=5, value=1))
outport.send(cc(C.RPN_LSB, channel=5, value=0))
for i in range(12):
outport.send(cc(C.DATA_INC, channel=5, value=0))
pb(0.15)
In [381]:
outport.send(cc(C.DATA_MSB, channel=5, value=0))
pb()
In [398]:
outport.send(cc(C.DATA_MSB, channel=5, value=0))
for i in range(1):
outport.send(cc(C.DATA_INC, channel=5, value=0))
pb(0.15)
outport.send(cc(C.RPN_LSB, channel=5, value=1))
for i in range(1):
pb(0.15)
outport.send(cc(C.RPN_LSB, channel=5, value=0))
for i in range(1):
outport.send(cc(C.DATA_INC, channel=5, value=0))
pb(0.15)
outport.send(cc(C.RPN_MSB, channel=5, value=1))
for i in range(1):
outport.send(cc(C.DATA_INC, channel=5, value=0))
pb(0.15)
outport.send(cc(C.RPN_MSB, channel=5, value=0))
outport.send(cc(C.RPN_MSB, channel=5, value=0x7F))
outport.send(cc(C.RPN_LSB, channel=5, value=0x7F))
for i in range(4):
outport.send(cc(C.DATA_INC, channel=5, value=0))
pb(0.15)
outport.send(cc(C.RPN_MSB, channel=5, value=0))
outport.send(cc(C.RPN_LSB, channel=5, value=0))
for i in range(4):
outport.send(cc(C.DATA_INC, channel=5, value=0))
pb(0.15)
Okay, that's some weird stuff.
In [399]:
outport.send(controls.gm_on())
In [487]:
def pb(iv=0.1):
outport.send(mido.Message('pitchwheel', pitch=0))
outport.send(mido.Message('note_on', note=64, velocity=90))
time.sleep(iv)
outport.send(mido.Message('pitchwheel', pitch=+8191))
time.sleep(iv)
outport.send(mido.Message('note_off', note=64))
time.sleep(iv)
Hypothesis: INC/DEC doesn't do anything until MSB/LSB has at least been set once. Even if that setting doesn't actually do anything.
In [468]:
XGRESET = mido.Message.from_hex("F0 43 10 4C 00 00 7E 00 F7")
In [494]:
outport.send(controls.gm_on()) # reset all
outport.send(cc(C.DATA_MSB, value=0)) # MSB = 0
pb() # PBS == 2
for i in range(2):
outport.send(cc(C.DATA_INC, value=0)) # MSB = 2
pb() # PBS == 2
outport.send(cc(C.RPN_MSB, value=0))
outport.send(cc(C.RPN_LSB, value=0)) # RPN -> PBS
pb() # PBS == 2
#outport.send(cc(C.DATA_MSB, value=0))
outport.send(cc(C.DATA_INC, value=0)) # MSB = 3, PBS = MSB
pb() # PBS == 3
outport.send(controls.gm_on())
# reset, RPN -> NULL, PBS = 2, but MSB stays 3 (?!)
pb() # PBS == 2
outport.send(cc(C.DATA_INC, value=0)) # MSB = 4
outport.send(cc(C.RPN_MSB, value=0)) # RPN -> PBS
outport.send(cc(C.RPN_LSB, value=0))
pb() # PBS == 2
outport.send(cc(C.DATA_INC, value=0)) # MSB = 5
pb() # PBS == 5
outport.send(cc(C.RPN_MSB, value=127))
outport.send(cc(C.RPN_LSB, value=127)) # RPN -> NULL
for i in range(4):
outport.send(cc(C.DATA_DEC, value=0)) # MSB = 1
outport.send(cc(C.RPN_MSB, value=0))
outport.send(cc(C.RPN_LSB, value=0)) # RPN -> PBS
pb() # PBS == 5
outport.send(cc(C.DATA_DEC, value=0)) # MSB = 0
pb() # PBS == 0
outport.send(mido.Message.from_hex("F0 43 10 4C 00 00 7E 00 F7"))
# XG ON
pb() # PBS == 2
outport.send(cc(C.RPN_MSB, value=0))
outport.send(cc(C.RPN_LSB, value=0)) # RPN -> PBS
outport.send(cc(C.DATA_INC, value=0)) # MSB = 1
pb() # PBS == 1
outport.send(mido.Message.from_hex("F0 43 10 4C 00 00 7F 00 F7"))
# XG Reset
pb() # PBS == 2
outport.send(cc(C.DATA_INC, value=0)) # MSB = 2
outport.send(cc(C.RPN_MSB, value=0))
outport.send(cc(C.RPN_LSB, value=0)) # RPN -> PBS
outport.send(cc(C.DATA_INC, value=0)) # MSB = 3
pb() # PBS == 3
outport.send(mido.Message.from_hex("F0 43 10 4C 00 00 7F 00 F7"))
# XG Reset, RPN -> NULL
pb() # PBS == 2
outport.send(cc(C.DATA_MSB, value=12)) # MSB = 12
pb() # PBS == 2
outport.send(cc(C.RPN_MSB, value=0))
outport.send(cc(C.RPN_LSB, value=0)) # RPN -> PBS
outport.send(cc(C.DATA_DEC, value=0)) # MSB = 11
pb() # PBS == 11
I've annotated the above with the results of the experiment and what I think's going on. Internally, INC/DEC seem to alter the value of an MSB variable, then set the MSB of the current RPN to what the value is now. It's the same variable, no matter which RPN is currently active; it doesn't get reset on GM_ON or XG ON or XG reset (which actually seems to work otherwise). The lesson: always set the MSB before using the INC/DEC, because weird things happen; it's definitely not the behaviour recommended in RPN-018.
Also, weird things happen if you go below zero, and above 127?? Might need even more testing!
In [525]:
outport.send(controls.gm_on()) # reset all
pb() # PBS == 2
outport.send(cc(C.RPN_MSB, value=0))
outport.send(cc(C.RPN_LSB, value=0)) # RPN -> PBS
outport.send(cc(C.DATA_MSB, value=0)) # MSB = 0
pb() # PBS == 0
outport.send(cc(C.DATA_DEC, value=0)) # MSB = 0? -1?
pb() # PBS == 0
outport.send(cc(C.DATA_DEC, value=0)) # MSB = 0? -1?
pb() # PBS == 0
outport.send(cc(C.DATA_INC, value=0)) # MSB = 1
pb() # PBS == 1
outport.send(cc(C.RPN_LSB, value=1)) # RPN -> Fine Tune
pb() # PBS == 1
outport.send(cc(C.DATA_DEC, value=0)) # MSB = 0
pb() # PBS == 1, but we've jumped down a semitone
outport.send(cc(C.RPN_LSB, value=0)) # RPN -> PBS
pb() # PBS == 1
outport.send(cc(C.DATA_DEC, value=0)) # MSB = 0? -1 ?
pb() # PBS == 1
outport.send(cc(C.DATA_DEC, value=0)) # MSB = 0? -1 ?
pb() # PBS == 1
outport.send(cc(C.DATA_DEC, value=0)) # MSB = 0? -1 ?
pb() # PBS == 1
outport.send(cc(C.DATA_INC, value=0)) # MSB = 1 ?
pb() # PBS == 1
outport.send(cc(C.DATA_INC, value=0)) # MSB = 2 ?
pb() # PBS == 2
In [540]:
outport.send(controls.gm_on()) # reset all
pb() # PBS == 2
outport.send(cc(C.RPN_MSB, value=0))
outport.send(cc(C.RPN_LSB, value=0)) # RPN -> PBS
outport.send(cc(C.DATA_MSB, value=4)) # MSB = 4
pb() # PBS == 4
outport.send(cc(C.RPN_LSB, value=1)) # RPN -> Fine Tune
pb() # PBS == 4
outport.send(cc(C.DATA_DEC, value=0)) # MSB = 4
pb() # PBS == 4, but we've jumped down almost a semitone
outport.send(cc(C.DATA_DEC, value=0)) # MSB = 3
outport.send(cc(C.DATA_DEC, value=0)) # MSB = 2
outport.send(cc(C.DATA_DEC, value=0)) # MSB = 1
outport.send(cc(C.DATA_DEC, value=0)) # MSB = 0
pb() # PBS == 4,
outport.send(cc(C.RPN_LSB, value=0)) # RPN -> PBS
pb() # PBS == 4
outport.send(cc(C.DATA_DEC, value=0)) # MSB = 0
pb() # PBS == 4
outport.send(cc(C.DATA_DEC, value=0)) # MSB = 0
pb() # PBS == 4
outport.send(cc(C.DATA_DEC, value=0)) # MSB = 0
pb() # PBS == 4
outport.send(cc(C.DATA_INC, value=0)) # MSB = 1
pb() # PBS == 1
outport.send(cc(C.DATA_INC, value=0)) # MSB = 2
pb() # PBS == 2
In [547]:
outport.send(controls.gm_on()) # reset all
pb() # PBS == 2
outport.send(cc(C.RPN_MSB, value=0))
outport.send(cc(C.RPN_LSB, value=1)) # RPN -> Fine Tune
outport.send(cc(C.DATA_MSB, value=127)) # MSB = 127
pb() # PBS == 2, but we've jumped up a semitone
outport.send(cc(C.RPN_LSB, value=0)) # RPN -> PBS
pb() # PBS == 2
outport.send(cc(C.DATA_INC, value=0)) # MSB = 127
pb() # PBS == 2
outport.send(cc(C.DATA_DEC, value=0)) # MSB = 126
pb() # PBS == 126 (it gets clamped to 24 in the tone generator)
Okay, so now it looks like if you go below 0 to "-1", you reach a state where it doesn't actually change the value anymore until you INC, and then it jumps to 1. So it's something like:
upon recieving DATA_DEC:
if MSB > 0:
MSB -= 1
set RPN data to MSB
upon receiving DATA_INC:
if MSB < 127:
MSB += 1
set RPN data to MSB
As a result if we change RPNs while MSB is at 0 already, then another DATA_DEC doesn't actually change the RPN data, which causes this odd behaviour; a DATA_INC increases the MSB to 1 and then sets the RPN data to 1, the second-lowest possible value. A weird edge case.
Does changing the RPN MSB also set the RPN LSB to zero?
In [573]:
outport.send(controls.gm_on()) # reset all
pb() # PBS == 2
outport.send(cc(C.RPN_MSB, value=0))
outport.send(cc(C.RPN_LSB, value=2)) # RPN -> Coarse Tune
outport.send(cc(C.DATA_MSB, value=62)) # MSB = 62
pb() # PBS == 2, down 2 semitones
outport.send(cc(C.RPN_MSB, value=1))
outport.send(cc(C.DATA_DEC, value=0)) # MSB = 61
pb() # PBS == 2, down 2 semitones
outport.send(cc(C.RPN_MSB, value=0)) # RPN -> Coarse Tune again
pb() # PBS == 2, down 2 semitones
outport.send(cc(C.DATA_DEC, value=0)) # MSB = 60
pb() # PBS == 2, down 4 semitones
outport.send(cc(C.DATA_DEC, value=0)) # MSB = 59
pb() # PBS == 2, down 5 semitones
No, it does not.
Does setting the LSB change the decrement?
In [575]:
outport.send(controls.gm_on()) # reset all
pb() # PBS == 2
outport.send(cc(C.RPN_MSB, value=0))
outport.send(cc(C.RPN_LSB, value=2)) # RPN -> Coarse Tune
outport.send(cc(C.DATA_MSB, value=62)) # MSB = 62
pb() # PBS == 2, down 2 semitones
outport.send(cc(C.DATA_LSB, value=32))
pb() # PBS == 2, down 2 semitones
outport.send(cc(C.DATA_DEC, value=0)) # MSB = 60
pb() # PBS == 2, down 4 semitones
outport.send(cc(C.DATA_DEC, value=0)) # MSB = 59
pb() # PBS == 2, down 5 semitones
No.
Does setting the LSB drag the MSB along with it?
In [581]:
outport.send(controls.gm_on()) # reset all
pb() # PBS == 2
outport.send(cc(C.RPN_MSB, value=0))
outport.send(cc(C.RPN_LSB, value=2)) # RPN -> Coarse Tune
outport.send(cc(C.DATA_MSB, value=65)) # MSB = 62
pb() # PBS == 2, up 1 semitone
outport.send(cc(C.RPN_LSB, value=0)) # RPN -> PBS
pb() # PBS ==2, up 1 semitone
outport.send(cc(C.DATA_LSB, value=42)) # LSB = 42
pb() # PBS == 2, still
outport.send(cc(C.DATA_DEC, value=0)) # MSB = 41
pb() # PBS == 41 (clamped to 24)
No.
F0 43 1n 4C aa aa aa dd .. dd F7
Parameter settings.
In [606]:
outport.send(controls.gm_on()) # reset all
pb()
outport.send(mido.Message.from_hex(
"F0 43 10 4C 00 00 04 03 F7"))
pb() # XG volume
outport.send(mido.Message.from_hex(
"F0 7F 7F 04 01 00 48 F7"))
pb() # USE volume
outport.send(mido.Message.from_hex(
"F0 43 11 4C 00 00 7F 00 F7"))
pb() # XG Reset
Seems like the only one that works is the XG Resets.
Does the DGX-505 respond to XG parameter requests?
In [607]:
inport = mido.open_input('DGX-505 MIDI 1')
In [634]:
list(inport.iter_pending())
Out[634]:
In [635]:
outport.send(mido.Message.from_hex(
"F0 43 40 4C 00 00 00 F7")) # request
outport.send(mido.Message.from_hex(
"F0 43 20 4C 00 00 01 F7")) # dump request
In [636]:
list(inport.iter_pending())
Out[636]:
Nope.
In [637]:
inport.close()
In [638]:
outport.close()
What about when we just power on?
In [657]:
outport = mido.open_output('DGX-505 MIDI 1')
In [658]:
pb() # MSB = 0, probably
In [659]:
outport.send(cc(C.DATA_INC, value=0)) # MSB = 1
pb() # PBS == 2
In [660]:
outport.send(cc(C.RPN_MSB, value=0))
outport.send(cc(C.RPN_LSB, value=0)) # RPN -> PBS
In [661]:
outport.send(cc(C.DATA_INC, value=0)) # MSB = 2
pb() # PBS == 2
In [662]:
outport.send(cc(C.DATA_INC, value=0)) # MSB = 3
pb() # PBS == 3
I guess MSB starts at 0.
In [663]:
outport.close()
In [ ]:
# power on
outport = mido.open_output('DGX-505 MIDI 1')
pb()
outport.send(cc(C.RPN_MSB, value=0))
outport.send(cc(C.RPN_LSB, value=2)) # RPN -> Coarse Tuning
outport.send(cc(C.DATA_DEC, value=0)) # MSB = 0
pb() # Coarse Tuning = 64
outport.send(cc(C.DATA_INC, value=0)) # MSB
I suppose now we need to test some other stuff.
In [3]:
o = mido.open_output('DGX-505 MIDI 1')
In [9]:
def pulse(port, sleep=0.5, note=60, velocity=100, channel=0):
port.send(mido.Message('note_on', note=note, velocity=velocity, channel=channel))
time.sleep(sleep)
o.send(mido.Message('note_on', note=note, velocity=0, channel=channel))
time.sleep(sleep)
In [44]:
pulse(o)
In [53]:
# Pitch Bend
o.send(controls.gm_on())
pulse(o)
o.send(mido.Message('pitchwheel', pitch=-8192))
pulse(o)
o.send(cc(C.RESET_CONTROLS, value=0))
pulse(o)
Pitch bend: check.
In [147]:
def pulsetest(port, m, channel=0, reset=True, voice=None, *args, **kwargs):
if reset:
port.send(controls.gm_on())
if voice is not None:
controls.multisend(port, controls.set_voice_numbers(voice, channel=channel))
pulse(port, channel=channel, *args, **kwargs)
port.send(m)
pulse(port, channel=channel, *args, **kwargs)
port.send(cc(C.RESET_CONTROLS, value=0, channel=channel))
pulse(port, channel=channel, *args, **kwargs)
In [151]:
pulsetest(o, cc(C.RELEASE, value=0), voice=103)
Release time: check
In [166]:
pulsetest(o, cc(C.ATTACK, value=80), voice=103)
In [167]:
pulsetest(o, cc(C.ATTACK, value=80), voice=120)
Attack time, check
In [180]:
pulsetest(o, cc(C.HARMONIC, value=127), voice=103)
Harmonic, check
In [187]:
pulsetest(o, cc(C.BRIGHTNESS, value=0), voice=1)
Now for Portamento Control
In [216]:
def porta(port, sleep=0.5, note1=60, note2=64, velocity=100, channel=0, reset=True, voice=None):
if reset:
port.send(controls.gm_on())
if voice is not None:
controls.multisend(port, controls.set_voice_numbers(voice, channel=channel))
port.send(mido.Message('note_on', note=note1, velocity=velocity, channel=channel))
time.sleep(sleep)
port.send(cc(C.PORTAMENTO_CTRL, value=note1))
port.send(mido.Message('note_on', note=note2, velocity=1, channel=channel))
time.sleep(sleep)
port.send(mido.Message('note_on', note=note2, velocity=0, channel=channel))
time.sleep(sleep)
port.send(mido.Message('note_on', note=note1, velocity=0, channel=channel))
time.sleep(sleep)
port.send(mido.Message('note_on', note=note1, velocity=velocity, channel=channel))
time.sleep(sleep)
port.send(cc(C.PORTAMENTO_CTRL, value=note1))
port.send(cc(C.RESET_CONTROLS, value=0, channel=channel))
port.send(mido.Message('note_on', note=note2, velocity=1, channel=channel))
time.sleep(sleep)
port.send(mido.Message('note_on', note=note2, velocity=0, channel=channel))
time.sleep(sleep)
port.send(mido.Message('note_on', note=note1, velocity=0, channel=channel))
time.sleep(sleep)
port.send(mido.Message('note_on', note=note1, velocity=velocity, channel=channel))
time.sleep(sleep)
port.send(cc(C.PORTAMENTO_CTRL, value=note1))
port.send(mido.Message('note_on', note=note2, velocity=1, channel=channel))
port.send(cc(C.RESET_CONTROLS, value=0, channel=channel))
time.sleep(sleep)
port.send(mido.Message('note_on', note=note2, velocity=0, channel=channel))
time.sleep(sleep)
port.send(mido.Message('note_on', note=note1, velocity=0, channel=channel))
time.sleep(sleep)
In [226]:
porta(o, voice=24)
In [769]:
def pbp(port, sleep=0.1, channel=0, note=64, velocity=100):
port.send(mido.Message('pitchwheel', pitch=0, channel=channel))
port.send(mido.Message('note_on', note=note, velocity=velocity, channel=channel))
time.sleep(sleep)
port.send(mido.Message('pitchwheel', pitch=+8191, channel=channel))
time.sleep(sleep)
port.send(mido.Message('note_off', note=note,channel=channel))
time.sleep(sleep)
In [281]:
o.send(controls.gm_on())
controls.multisend(o, controls.set_voice_numbers(1))
o.send(cc(C.RPN_MSB, value=0))
o.send(cc(C.RPN_LSB, value=2))
pbp(o)
o.send(cc(C.DATA_MSB, value=65))
pbp(o)
o.send(cc(C.RESET_CONTROLS, value=0))
pbp(o)
pbp(o)
o.send(cc(C.RPN_MSB, value=0))
o.send(cc(C.DATA_INC, value=0))
pbp(o)
o.send(cc(C.RPN_LSB, value=2))
o.send(cc(C.DATA_INC, value=0))
pbp(o)
In [283]:
i = mido.open_input('DGX-505 MIDI 1')
In [285]:
list(i.iter_pending())
Out[285]:
In [286]:
d1 = list(i.iter_pending())
In [291]:
cs = controlstate.MidiControlState()
In [294]:
def grab():
return list(cs.feed_message(m) for m in i.iter_pending())
In [296]:
w1 = [cs.feed_message(m) for m in d1]
In [298]:
o.send(controls.master_vol(2))
In [303]:
w2 = grab()
In [305]:
all(a.message == b.message for a, b in zip(w1, w2))
Out[305]:
In [306]:
grab()
Out[306]:
In [307]:
w3 = grab()
In [309]:
[(a, b) for a, b in zip(w2, w3) if a.message != b.message]
Out[309]:
In [310]:
o.send(mido.Message.from_hex("F0 43 10 4C 00 00 7E 00 F7")) # XG ON
In [311]:
w4 = grab()
In [312]:
[(a, b) for a, b in zip(w3, w4) if a.message != b.message]
Out[312]:
In [313]:
grab()
Out[313]:
In [314]:
w5 = grab()
In [315]:
[(a, b) for a, b in zip(w4, w5) if a.message != b.message]
Out[315]:
In [316]:
o.send(mido.Message.from_hex("F0 43 10 4C 00 00 7F 00 F7")) # XG RESET
In [317]:
w6 = grab()
In [318]:
[(a, b) for a, b in zip(w5, w6) if a.message != b.message]
Out[318]:
Hmmm, it looks like XG reset also resets the MIDI Master Tuning, as well!
In [320]:
o.send(controls.master_tune_val(100))
In [321]:
o.send(controls.gm_on())
In [322]:
o.send(mido.Message.from_hex("F0 43 10 4C 00 00 7E 00 F7")) # XG ON
In [326]:
o.send(mido.Message.from_hex("F0 43 10 4C 00 00 7F 00 F7")) # XG RESET
Seems to be the only difference
In [327]:
grab()
Out[327]:
In [328]:
i.close()
Let's review:
There are melody voices and there are rhythym voices (drum kits).
The specification says:
If the tone generator does not have a drum kit corresponding to the specified program number, it will ignore the message and continue to use the current drum kit.
If Program change makes a change from normal voice to drum kit, the part mode shall revert to what it was before normal mode was invoked. Upon receipt of XG System ON, the system shall virtually set this "revert-to" mode to Drum Setup 2 for all Parts 1 to 16, except for Part 10.
Bank MSB:
MSB | |
---|---|
00 |
Melody voice |
01 –3F |
Model-exclusive area |
40 |
SFX voice |
41 –77 |
Reserved for XG extensions |
78 |
GM L2 Rhythm Kit |
79 |
GM L2 Melody voice |
7A –7D |
Reserved for XG extensions |
7E |
SFX kit |
7F |
Rhythm kit |
Bank LSB is an extension area.
If support is included for one or more voices in an extension bank, then all the other program change numbers in that bank are filled with the corresponding voices of Bank 0 (basic voices)
Note 1: By default, channel 10 plays rhythm voices while other channels use bank 0 melody voices (as in GM Level 1)
Note 2: If the new Bank Select MSB is
00
(melody voice) but the tone generator does not support the melody voice corresponding to the last recieved Bank Select LSB, the channel reverts to the Bank Select LSB corresponding to its most recently played melody voice.Note 3: If the new Bank Select MSB is
7F
(rhythm voice) the tone generator unconditionally uses LSB00
without using the most recently received Bank Select LSB. If the tone generator does not support a drum kit corresponding to the channel's most recently received Program Change, the channel will revert to the Program Change corresponding to its most recently played rhythm kit.Note 4: If a Bank Select MSB value of
01
–77
or7A
–7E
(model-exclusive area, SFX voice, or XG extension voice) is received and the tone generator does not have a voice corresponding to the last received LSB and Program change, the tone generator shall produce no sound for that channel regardless of subsequent Key On messages.In the case where a melody voice is being changed first to a voice in bank-LSB A and then to a voice in bank-LSB B, and the change to A is possible but the change to B is not possible, A will be used as the substitute for B. If neither the change to A nor the change to B is possible, the voice of the previous bank will [be] substituted for A and B.
In the case where a rhythm voice is being changed first program number kit A and then to program number kit B, and the change to A is possible but the change to B is not possible, A will be used as the substitute for B. If neither the change to A nor the change to B is possible, the voice of the previous bank will [be] substituted for A and B.
We have the following supported kits:
No. | Name | MSB | LSB | Prog | Note 40 |
---|---|---|---|---|---|
122 | Standard Kit 1 | 127 | 0 | 0 | Snare H Hard |
123 | Standard Kit 2 | 127 | 0 | 1 | Snare H Hard 2 |
124 | Room Kit | 127 | 0 | 8 | SD Room H |
125 | Rock Kit | 127 | 0 | 16 | SD Rock Rim |
126 | Electronic Kit | 127 | 0 | 24 | SD Rock H |
127 | Analog Kit | 127 | 0 | 25 | Analog Snare 2 |
128 | Dance Kit | 127 | 0 | 27 | AnSD Ana+Acoustic |
129 | Jazz Kit | 127 | 0 | 32 | SD Jazz M |
130 | Brush Kit | 127 | 0 | 40 | Brush Tap |
131 | Symphony Kit | 127 | 0 | 48 | Marching Sn H |
132 | SFX Kit 1 | 126 | 0 | 0 | |
133 | SFX Kit 2 | 126 | 0 | 1 | Scratch |
Note 40 is different for each of them.
In [330]:
i = mido.open_input('DGX-505 MIDI 1')
In [335]:
grab()
Out[335]:
In [361]:
progs = (0,1,8,16,24,25,27,32,40,48)
In [341]:
o.send(cc(C.BANK_MSB, value=127))
In [343]:
o.send(mido.Message('program_change', program=0))
In [488]:
for p in progs:
o.send(mido.Message('program_change', program=p))
o.send(mido.Message('note_on', note=40, velocity=33))
time.sleep(0.2)
In [ ]:
o.send(cc(C.BANK_LSB, value=127))
In [8]:
from commons.messages import voices
In [402]:
def voice_list(prog):
v = []
for l in range(128):
try:
v.append(voices.from_bank_program(0, l, prog))
except KeyError:
pass
return v
In [396]:
[voice_list(x) for x in progs]
Out[396]:
In [517]:
organvoices = voice_list(16)
print('\n'.join(x.voice_string_extended() for x in organvoices))
There are a lot of different organ voices for program 16, MSB 0.
MSB 127 program 16 is the Rock Kit.
Let's set channel 1 to the DrawOrg, Channel 2 to the Jazz Organ 1.
In [418]:
controls.multisend(o, controls.set_voice_numbers(176, channel=1))
controls.multisend(o, controls.set_voice_numbers(19, channel=2))
In [540]:
pulse(o, channel=1, note=40)
In [506]:
pulse(o, channel=2, note=40)
In [497]:
o.send(mido.Message('program_change', program=16, channel=0))
Let's set Channel 0 to the rock kit, program 16:
In [498]:
pulse(o, channel=0, note=40)
Now, Bank LSB should be totally ignored for drum kits.
In [516]:
o.send(cc(C.BANK_MSB, value=127, channel=0))
o.send(cc(C.BANK_LSB, value=0, channel=0))
o.send(mido.Message('program_change', program=16, channel=0))
pulse(o, channel=0, note=40)
o.send(cc(C.BANK_LSB, value=112, channel=0))
o.send(mido.Message('program_change', program=16, channel=0))
pulse(o, channel=0, note=40)
o.send(cc(C.BANK_MSB, value=0, channel=0))
o.send(mido.Message('program_change', program=16, channel=0))
pulse(o, channel=0, note=40)
o.send(cc(C.BANK_MSB, value=127, channel=0))
o.send(mido.Message('program_change', program=16, channel=0))
pulse(o, channel=0, note=40)
o.send(cc(C.BANK_MSB, value=0, channel=0))
o.send(mido.Message('program_change', program=16, channel=0))
pulse(o, channel=0, note=40)
In [514]:
The Bank LSB is treated as 0 (or at least not recognised for the drum kits), but it is still remembered so that if we change the MSB, the LSB value is used then. Setting the MSB does not reset the LSB to zero.
In [518]:
olsbs = [v.lsb for v in organvoices]
In [519]:
olsbs
Out[519]:
In [538]:
o.send(cc(C.BANK_LSB, value=114, channel=0))
o.send(mido.Message('program_change', program=16, channel=0))
pulse(o, sleep=0.2, channel=0, note=40)
o.send(cc(C.BANK_LSB, value=115, channel=0))
o.send(mido.Message('program_change', program=16, channel=0))
pulse(o, sleep=0.2, channel=0, note=40)
In [676]:
o.send(cc(C.BANK_MSB, value=0, channel=0))
for l in range(120):
print(l, end=" ")
o.send(cc(C.BANK_LSB, value=l, channel=0))
o.send(mido.Message('program_change', program=16, channel=0))
pulse(o, sleep=0.1, channel=0, note=40)
It seems like when the LSB is not supported, it falls back to zero, but I'm willing to bet it still remembers it.
In [702]:
o.send(cc(C.BANK_LSB, value=0, channel=0))
o.send(mido.Message('program_change', program=16, channel=0))
pulse(o, sleep=0.2, channel=0, note=40)
o.send(cc(C.BANK_LSB, value=8, channel=0))
o.send(mido.Message('program_change', program=16, channel=0))
pulse(o, sleep=0.2, channel=0, note=40)
o.send(cc(C.BANK_LSB, value=32, channel=0))
o.send(mido.Message('program_change', program=16, channel=0))
pulse(o, sleep=0.2, channel=0, note=40)
o.send(cc(C.BANK_LSB, value=8, channel=0))
o.send(mido.Message('program_change', program=16, channel=0))
pulse(o, sleep=0.2, channel=0, note=40)
# change to violins
o.send(mido.Message('program_change', program=40, channel=0))
pulse(o, sleep=0.2, channel=0, note=40)
o.send(cc(C.BANK_LSB, value=0, channel=0))
o.send(mido.Message('program_change', program=40, channel=0))
pulse(o, sleep=0.2, channel=0, note=40)
o.send(cc(C.BANK_LSB, value=8, channel=0))
o.send(mido.Message('program_change', program=40, channel=0))
pulse(o, sleep=0.2, channel=0, note=40)
Yep, it seems like it.
In [553]:
o.send(cc(C.BANK_LSB, value=114, channel=0))
o.send(mido.Message('program_change', program=16, channel=0))
pulse(o, sleep=0.2, channel=0, note=40)
o.send(cc(C.RESET_CONTROLS, value=0, channel=0))
o.send(mido.Message('program_change', program=16, channel=0))
pulse(o, sleep=0.2, channel=0, note=40)
Yep, and the bank doesn't get reset either.
What about unsupported banks?
In [677]:
o.send(cc(C.BANK_MSB, value=0, channel=0))
o.send(cc(C.BANK_LSB, value=114, channel=0))
o.send(mido.Message('program_change', program=16, channel=0))
pulse(o, sleep=0.2, channel=0, note=40)
o.send(cc(C.BANK_MSB, value=62, channel=0))
o.send(mido.Message('program_change', program=16, channel=0))
pulse(o, sleep=0.2, channel=0, note=40)
o.send(cc(C.BANK_LSB, value=116, channel=0))
o.send(mido.Message('program_change', program=16, channel=0))
pulse(o, sleep=0.2, channel=0, note=40)
o.send(cc(C.BANK_MSB, value=0, channel=0))
o.send(mido.Message('program_change', program=16, channel=0))
pulse(o, sleep=0.2, channel=0, note=40)
o.send(cc(C.BANK_LSB, value=114, channel=0))
o.send(mido.Message('program_change', program=16, channel=0))
pulse(o, sleep=0.2, channel=0, note=40)
They're silent.
In [577]:
allvoices = [voices.from_number(x) for x in range(1, 495)]
In [605]:
zerovoices = sorted((v for v in allvoices if v.lsb == 0), key=lambda v: (v.msb, v.prog))
In [587]:
zerovoices
Out[587]:
In [592]:
for v in allvoices:
try:
voices.from_bank_program(v.msb, 0, v.prog)
except KeyError:
print(v)
In [606]:
sorted((v for v in allvoices if v.msb != 0 and v.lsb != 0), key=lambda v: (v.msb, v.lsb, v.prog))
Out[606]:
MSB 0 always has a fallback voice for any program.
What if the program is not supported?
In [706]:
o.send(cc(C.BANK_MSB, value=127, channel=0))
for p in range(128):
print(p, end=" ")
o.send(mido.Message('program_change', program=p, channel=0))
pulse(o, sleep=0.1, channel=0, note=40)
In [707]:
o.send(cc(C.BANK_MSB, value=126, channel=0))
o.send(cc(C.BANK_LSB, value=0, channel=0))
for p in range(128):
print(p, end=" ")
o.send(mido.Message('program_change', program=p, channel=0))
pulse(o, sleep=0.05, channel=0, note=84)
In [596]:
progs
Out[596]:
In [638]:
o.send(cc(C.BANK_MSB, value=127, channel=0))
o.send(mido.Message('program_change', program=0, channel=0))
pulse(o, sleep=0.2, channel=0, note=40)
o.send(mido.Message('program_change', program=3, channel=0))
pulse(o, sleep=0.2, channel=0, note=40)
o.send(mido.Message('program_change', program=16, channel=0))
pulse(o, sleep=0.2, channel=0, note=40)
o.send(mido.Message('program_change', program=3, channel=0))
pulse(o, sleep=0.2, channel=0, note=40)
If the program is not supported for drum kit, it just won't change the program.
In [670]:
o.send(cc(C.BANK_MSB, value=126, channel=0))
o.send(cc(C.BANK_LSB, value=0, channel=0))
o.send(mido.Message('program_change', program=0, channel=0))
pulse(o, sleep=0.2, channel=0, note=84)
o.send(mido.Message('program_change', program=6, channel=0))
pulse(o, sleep=0.2, channel=0, note=84)
o.send(mido.Message('program_change', program=1, channel=0))
pulse(o, sleep=0.2, channel=0, note=84)
o.send(mido.Message('program_change', program=3, channel=0))
pulse(o, sleep=0.2, channel=0, note=84 )
If the program is not supported for SFX kit, it just won't play at all.
In [635]:
voice_list(40)
Out[635]:
In [651]:
o.send(cc(C.BANK_MSB, value=64, channel=0))
for p in range(128):
print(p, end=" ")
o.send(mido.Message('program_change', program=p, channel=0))
pulse(o, sleep=0.05, channel=0, note=40)
In [674]:
o.send(cc(C.BANK_MSB, value=64, channel=0))
o.send(cc(C.BANK_LSB, value=0, channel=0))
o.send(mido.Message('program_change', program=70, channel=0))
pulse(o, sleep=0.2, channel=0, note=84)
o.send(mido.Message('program_change', program=71, channel=0))
pulse(o, sleep=0.2, channel=0, note=84)
o.send(mido.Message('program_change', program=64, channel=0))
pulse(o, sleep=0.2, channel=0, note=84)
o.send(mido.Message('program_change', program=71, channel=0))
pulse(o, sleep=0.2, channel=0, note=84 )
And it seems like that for the SFX voices as well.
In [659]:
o.send(cc(C.BANK_MSB, value=64, channel=0))
for l in range(128):
print(l, end=" ")
o.send(cc(C.BANK_LSB, value=l, channel=0))
o.send(mido.Message('program_change', program=70, channel=0))
pulse(o, sleep=0.1, channel=0, note=40)
The LSB fallback works. for SFX voices.
In [673]:
o.send(cc(C.BANK_MSB, value=126, channel=0))
for l in range(10):
print(l, end=" ")
o.send(cc(C.BANK_LSB, value=l, channel=0))
o.send(mido.Message('program_change', program=0, channel=0))
pulse(o, sleep=0.1, channel=0, note=84)
In [675]:
o.send(cc(C.BANK_MSB, value=126, channel=0))
o.send(cc(C.BANK_LSB, value=1, channel=0))
o.send(mido.Message('program_change', program=0, channel=0))
pulse(o, sleep=0.2, channel=0, note=84)
o.send(mido.Message('program_change', program=6, channel=0))
pulse(o, sleep=0.2, channel=0, note=84)
o.send(mido.Message('program_change', program=1, channel=0))
pulse(o, sleep=0.2, channel=0, note=84)
o.send(mido.Message('program_change', program=3, channel=0))
pulse(o, sleep=0.2, channel=0, note=84 )
but not for SFX kits.
In [743]:
o.send(controls.gm_on())
pulse(o, sleep=0.2, channel=0, note=60)
o.send(mido.Message('program_change', program=16, channel=0))
pulse(o, sleep=0.2, channel=0, note=60)
o.send(cc(C.BANK_LSB, value=113, channel=0))
o.send(mido.Message('program_change', program=16, channel=0))
pulse(o, sleep=0.2, channel=0, note=60)
o.send(cc(C.BANK_LSB, value=0, channel=0))
o.send(mido.Message('program_change', program=16, channel=0))
pulse(o, sleep=0.2, channel=0, note=60)
# drum channel
pulse(o, sleep=0.2, channel=9, note=40)
o.send(mido.Message('program_change', program=16, channel=9))
pulse(o, sleep=0.2, channel=9, note=40)
o.send(mido.Message('program_change', program=0, channel=9))
pulse(o, sleep=0.2, channel=9, note=40)
o.send(mido.Message.from_hex("F0 43 10 4C 00 00 7E 00 F7")) # XG ON
pulse(o, sleep=0.2, channel=0, note=60)
o.send(mido.Message('program_change', program=16, channel=0))
pulse(o, sleep=0.2, channel=0, note=60)
o.send(cc(C.BANK_LSB, value=113, channel=0))
o.send(mido.Message('program_change', program=16, channel=0))
pulse(o, sleep=0.2, channel=0, note=60)
o.send(cc(C.BANK_LSB, value=0, channel=0))
o.send(mido.Message('program_change', program=16, channel=0))
pulse(o, sleep=0.2, channel=0, note=60)
# drum channel
pulse(o, sleep=0.2, channel=9, note=40)
o.send(mido.Message('program_change', program=16, channel=9))
pulse(o, sleep=0.2, channel=9, note=40)
o.send(mido.Message('program_change', program=0, channel=9))
pulse(o, sleep=0.2, channel=9, note=40)
o.send(mido.Message.from_hex("F0 43 10 4C 00 00 7F 00 F7")) # XG RESET
pulse(o, sleep=0.2, channel=0, note=60)
o.send(mido.Message('program_change', program=16, channel=0))
pulse(o, sleep=0.2, channel=0, note=60)
o.send(cc(C.BANK_LSB, value=113, channel=0))
o.send(mido.Message('program_change', program=16, channel=0))
pulse(o, sleep=0.2, channel=0, note=60)
o.send(cc(C.BANK_LSB, value=0, channel=0))
o.send(mido.Message('program_change', program=16, channel=0))
pulse(o, sleep=0.2, channel=0, note=60)
# drum channel
pulse(o, sleep=0.2, channel=9, note=40)
o.send(mido.Message('program_change', program=16, channel=9))
pulse(o, sleep=0.2, channel=9, note=40)
o.send(mido.Message('program_change', program=0, channel=9))
pulse(o, sleep=0.2, channel=9, note=40)
Does the INC DEC weirdness leak across channels.
In [782]:
o.send(controls.gm_on())
controls.multisend(o, controls.set_voice_numbers(1))
pbp(o)
o.send(cc(C.DATA_MSB, value=59))
o.send(cc(C.RPN_MSB, value=0))
o.send(cc(C.RPN_LSB, value=2))
pbp(o)
o.send(cc(C.DATA_INC, value=0))
pbp(o)
controls.multisend(o, controls.set_voice_numbers(1, channel=2))
pbp(o, channel=2)
#o.send(cc(C.DATA_MSB, value=59, channel=1))
o.send(cc(C.RPN_MSB, value=0, channel=2))
o.send(cc(C.RPN_LSB, value=2, channel=2))
pbp(o, channel=2)
o.send(cc(C.DATA_INC, value=0, channel=2))
pbp(o, channel=2)
No, each channel has its own weirdness variable completely separate.
In [9]:
controls.xg_on()
Out[9]:
In [17]:
ii = mido.open_input('DGX-505 MIDI 1')
In [21]:
def grabm(p):
return list(p.iter_pending())
In [11]:
o = mido.open_output('DGX-505 MIDI 1')
In [66]:
for i in range(16):
o.send(controls.master_tune_val(-100))
controls.multisend(o, controls.set_voice_numbers(60))
pulse(o, 0.1)
o.send(controls.xg_parameter_change(0, 0, 0x7E, 0, n=i))
pulse(o, 0.1)
o.send(controls.xg_parameter_change(0, 0, 0x7F, 0, n=i))
pulse(o, 0.1)
In [60]:
grabm(ii)
Out[60]:
In [68]:
o.send(controls.cc(controls.Control.LOCAL, value=0x70, channel=15))
In [67]:
o.send(controls.gm_on())
LOCAL is not reset by XG on, GM on, or XG Reset
What's the default for Master Volume? I assume 7F
In [76]:
o.send(controls.master_vol(100))
pulse(o, 0.1)
o.send(controls.xg_parameter_change(0, 0, 0x7E, 0, n=i))
pulse(o, 0.1)
o.send(controls.master_vol(127))
pulse(o, 0.1)
o.send(controls.xg_parameter_change(0, 0, 0x7E, 0, n=i))
pulse(o, 0.1)
How does portamento control work with changed voices or drum kits?
In [204]:
o.send(controls.xg_reset())
controls.multisend(o, controls.set_voice_numbers(60))
o.send(mido.Message('note_on', note=60))
time.sleep(0.5)
o.send(controls.cc(controls.Control.PORTAMENTO_CTRL, value=60))
o.send(mido.Message('note_on', note=60))
#o.send(mido.Message('note_on', note=61))
time.sleep(0.5)
o.send(mido.Message('note_off', note=60))
o.send(mido.Message('note_off', note=60))
#o.send(mido.Message('note_off', note=61))
Sending the same note seems to require turning the note off twice.
In [ ]:
In [162]:
o.send(controls.xg_reset())
controls.multisend(o, controls.set_voice_numbers(60))
o.send(mido.Message('note_on', note=60))
time.sleep(0.5)
controls.multisend(o, controls.set_voice_numbers(101))
o.send(mido.Message('note_on', note=60))
time.sleep(0.5)
o.send(controls.cc(controls.Control.PORTAMENTO_CTRL, value=60))
o.send(mido.Message('note_on', note=61))
time.sleep(0.5)
o.send(controls.cc(controls.Control.PORTAMENTO_CTRL, value=60))
o.send(mido.Message('note_on', note=61))
time.sleep(0.5)
o.send(mido.Message('note_off', note=61))
time.sleep(0.5)
o.send(mido.Message('note_off', note=61))
In [81]:
o.send(controls.xg_reset())
controls.multisend(o, controls.set_voice_numbers(122))
o.send(mido.Message('note_on', note=29))
time.sleep(0.5)
o.send(controls.cc(controls.Control.PORTAMENTO_CTRL, value=29))
o.send(mido.Message('note_on', note=28))
time.sleep(0.5)
o.send(mido.Message('note_off', note=28))
Portamento does not work with drum kits.
In [82]:
o.send(controls.xg_reset())
controls.multisend(o, controls.set_voice_numbers(133))
o.send(mido.Message('note_on', note=36))
time.sleep(0.5)
o.send(controls.cc(controls.Control.PORTAMENTO_CTRL, value=36))
o.send(mido.Message('note_on', note=37))
time.sleep(0.5)
o.send(mido.Message('note_off', note=37))
Or SFX kits.
In [125]:
o.send(controls.xg_reset())
controls.multisend(o, controls.set_voice_numbers(474))
o.send(mido.Message('note_on', note=36))
time.sleep(0.1)
o.send(controls.cc(controls.Control.PORTAMENTO_CTRL, value=36))
o.send(mido.Message('note_on', note=37))
time.sleep(0.5)
o.send(mido.Message('note_off', note=37))
SFX voices seem fine, though.
In [127]:
o.send(controls.xg_reset())
controls.multisend(o, controls.set_voice_numbers(434))
o.send(mido.Message('note_on', note=60, velocity=127))
time.sleep(0.2)
o.send(controls.cc(controls.Control.PORTAMENTO_CTRL, value=60))
o.send(mido.Message('note_on', note=61, velocity=127))
time.sleep(0.5)
o.send(mido.Message('note_off', note=61))
time.sleep(1)
o.send(cc(C.ATTACK, value=50))
o.send(cc(C.BRIGHTNESS, value=127))
o.send(cc(C.RELEASE, value=127))
o.send(mido.Message('note_on', note=60, velocity=127))
time.sleep(0.2)
o.send(controls.cc(controls.Control.PORTAMENTO_CTRL, value=60))
o.send(mido.Message('note_on', note=61, velocity=127))
time.sleep(0.5)
o.send(mido.Message('note_off', note=61))
In [71]:
voices.from_number(434)
Out[71]:
I suppose if the note runs out it also doesn't apply? I dunno.
In [ ]: