When there's no sustain, the release step needs to be calculated from
the attack level, not the sustain level. Otherwise, contrary to intent,
this leads to the actual release taking a loooonnngg time.
A note can be placed in the center (panning=0) or moved to just the left
(panning=1) or right (panning=-1) channels. Fractional panning values
place it partially in both channels.
Now the vibrato 'units' are 1.0 = one octave, 1/12 = one semitone,
1/1200 = one cent. Before, the units were somewhat arbitrary and were not
perceptually "symmetrical" around the base frequency.
For vibrato_depth = 1/12 and base frequency of 440,
before: pitch from 403.33 to 476.67Hz, not corresponding to any notes
after: pitch from 415.30 to 466.16Hz, corresponding to G# and A#
this has the side effect of making some notes more accurate, the new
frequency= value in the test is closer to the true midi frequency of
830.609...Hz.
and re-vamp overall envelope calculation again.
Now, if you set a low overall attack level like 0.2 this avoids the
"diminishing volume" effect when many notes sound at once. You need
simply choose a maximum attack level that is appropriate for the max
number of voices that will actually be played.
.. and account releasing notes at their sustain level until they're
done.
this ameliorates the effect where multiple releasing notes
don't seem to actually be releasing, but stay at a constant volume.
This class allows much more expressive sound synthesis:
* tremolo & vibrato
* arbitrary frequency
* different evelope & waveform per note
* all properties dynamically settable from Python code
This works for me (tested playing midi to raw files on host computer, as
well as a variant of the nunchuk instrument on pygamer)
it has to re-factor how/when MIDI reading occurs, because reasons.
endorse new test results
.. and allow `-1` to specify a note with no sustain (plucked)
In contrast to MidiTrack, this can be controlled from Python code,
turning notes on/off as desired.
Not tested on real HW yet, just the acceptance test based on checking
which notes it thinks are held internally.
a waveform object (array of 'h') can be passed in, replacing the
standard square wave. This waveform must be a 'single cycle waveform'
and some obvious things to pass in are sine, triangle or sawtooth waves,
but you can construct whatever you like.