I've just been thinking. Currently, I implemented the pitch wheel message to affect currently playing and future note on notes when it's received. Is it supposed to work like that(like a CC channel does as well)? Or is it supposed to be only applied to running notes, with future notes being automatically given a pitch wheel bending of being unaffected?
... Or is it supposed to be only applied to running notes, with future notes being automatically given a pitch wheel bending of being unaffected?
No, pitch bend works like other channel-wide messages. That is it affects current and also future notes. Some software (e.g. abc2midi) even use pitch bend messages to achieve different scales/tunings.
OK. What about the other MIDI messages (the two aftertouch commands) and CC values?
Does the channel aftertouch command(D*) set the aftertouch value for all running notes on a channel? Or is it something that's additive with the normal polyphonic aftertouch(A*)? Although UniPCemu doesn't implement it yet, other than storing it's value.
Or is it something that's additive with the normal polyphonic aftertouch(A*)?
It depends. Since they are completely different messages and despite the fact that even earliest Midi specification defined HOW they work, it was never specified WHAT they do exactly. So in theory these messages can trigger even completely different effects. If you look at BassMidi's specification you can see that you can assign different amount of filter cutoff/pitch/vibrato/volume effects to them independently. http://www.un4seen.com/doc/#bassmidi/BASS_MID … treamEvent.html
Be aware that by default aftertouch messages will have no effect at all in case of most synths. In order aftertouch messages to do something you need to set aftertouch related parameters (in case of Roland gear by using SysEx messages).
Maybe because of the above written aftertouch messages are rarely used by properly authored standard Midi files.
In reality most Midi controllers/keyboards implement either monophonic channel aftertouch or polyphonic key aftertouch. Typically only more expensive controllers can send polyphonic key aftertouch since it requires a more expensive mechanism.
I've just been thinking about it and the other generators. Are all modulators supposed to update in realtime? So when a SF2 modulator source(say, CC1, CC7 etc.) is changed, do the running notes immediately change their parameters that depends on said CC? Of course the one exception only being the bank and instrument numbers(bank taking effect when loading a new program, while the program(of the bank during the loading of said program) only is applied to new notes during note on commands? And all other CC inputs are applied to the note on(new notes starring) and running(waiting for note off) when they're changed, with immediate response?
UniPCemu currently only applies the modulators and CC inputs for them when a note on is received (for said selected note on it's voice(s) only), but ignores any changes made to the CC inputs until a new note on command is received, after which said note will use the changed CCs for it's parameters for the duration of the note.
Currently the only parameter that's actually constantly updated (for each voice sample rendering callback) is the pitch bend parameter(it's 14-bit number). All others are handled as mentioned above(effectively latched for all allocated voices on a note on message).
For example:
CC7=7F
Note on A4
CC7=6F
Note on A3
CC7=5F
Note off A4
Note off A3
That will sound A4 at volume 7F. Then A3 at volume 6F(A4 stays 80). Then A4 is releases. Then A3 is released.
That's what's happening with UniPCemu.
Pitch bends are realtime however, affecting the running notes. But the CC inputs only affect notes that start on note on messages(latching their precalcs into the voice attenuation based on the generators, modulators and CC inputs).
Also, only the 5-ish modulators specified in the default modulators(with those exact modulator source-dest fields(16-bit word) are currently used. Any other modulator source-dest-transform combination is currently ignored(it can't even read them, since the lookup of a modulator is, like the generators, just a simple 16-bit is-equal comparision to find the modulators). So no linking or custom source-dest-transform other than the specified default modulators is recognised in the soundfont(the only thing being adjustable being the amount field as a preset(if specified) and/or instrument modulator).
Modified all the implemented modulators to allow dynamic updating of their parameter sources(CC controllers now added). Of course with the exception of bank and program change.
Although the voices aren't calling the update function yet when the continuous controllers are changed, only being called upon to update when the voice is started to render(on the note on command processing).
I just need to make all voices of a channel call the update function when the channel's CC is changed, then that's also implemented(at least the attenuation modulators).
Do the chorus and reverb CC affect running notes(voices that are already rendering active volume envelopes) as well? It sounds a bit strange to make them update running notes, changing the amount of chorus/reverb during a note that's already playing?
Although I still need to change the modulators to be able to do anything else than what the default modulators explain(which is currently the only modulator that's supported, only allowing to change the amount field). The destination operand of those are also completely ignored and assumed to be added to initial attenuation.
I've just been thinking. The panning and mod are calculated in 0.1% units of output voltage, with a range of -100% to +100%. Doesn't that mean that centered (50% left and 50% right) only produces half the voltage of 100% on hard left or hard right panning? So panned sound is (much) harder than centered (unpanned) sound, playing the samples at double the voltage on one channel compared to unpanned?
Is that even supposed to be the case?
Just found out that the MIDI reverb effect takes quite a lot of memory(around 20MB), compared to the basic channels themselves(around 1-2MB). It's also quite a large chunk compared to PSP RAM(less than 32MB). For this reason, I've now disabled the reverb effect on PSP builds.
OK. Just improved the MIDI modulators to be more flexible, allowing other custom (only non-linked atm) modulators to be used.
Then I noticed an error in the Soundfont 2.04 spec:
MIDI Continuous Controller 7 to Initial Attenuation
Source Enumeration = 0x0582 (type=1, P=0, D=1, CC=1, index = 7)
When you compare the source enumeration(0x582) with the explanation at the start of section 8.2, you'll notice that the source enumeration on the controller is pointing to CC2 instead of CC7!
So I thought, why would my audio be silent? Then I noticed that incorrect controller setting! And since during reset said controller is never set, nor by software using MIDI commands, it will use the value 0 for said default controller(since CC2=0 by default)! Thus muting volume completely on the default volume!
Having fixed said default modulator, the volume is once again fixed. But it now will use all modulator combinations as specified in the Soundfont file(although only updating some of them during runtime).
Edit: It also seems to fix the wrong pitches that happened in various test MID files I tried to play(mostly touhou songs (for responsiveness) and Jazz Jackrabit (the Jazz Jackrabbit MIDI project).
Hmmm... I now seem to notice that the pitch bend in Jazz Jackrabbit level 1-3 from the MIDI project doesn't seem to pitch bend anymore?
Just implemented the MIDI pitch wheel sensitivity RPN to the synth. I'd assume that the sensitivity cents field(it's LSB) is divided by 100 and added to the MSB to provide the actual input to the modulators?
Although the sweep itself seems to sound a little bit rough for some reason? Like scratching on a table?
OK. I've noticed one strange little thing: what is the destination node of the pitch wheel modulator? What is it's correct destination supposed to be? The documentation says 'Initial Pitch', but no such generator is mentioned?
I currently went the known way(synthfont) and applied it to the finetune parameter without any limits(it previously was limited from 0 to 12700 cents).
Edit: OK. Pitch bend seems to work with this setting, but I notice something strange: When the pitch is being bended dynamically(changing through time in a few seconds sweeping up or down), it seems to perform some weird kind of raspy sound? It's actually performing the sample updates while it's changed(every 42 samples), so it should pretty much be realtime?
It simply adds said amount of cents to the sample speedup (in cents), which is applied by converting to a factor, which is simply applied to the curent sample that's playing in realtime. So the first sample will use n(for example), then the second n+1 etc(with a linear ramp). Then the first sample will multiply the sample number(which is monotonically increasing) with 0 cents, the second with 1(cents) etc.
Although it won't take the previous sample position into account. The first sample will use 0*n, then the second sample 1*n, the third sample 2*n etc. until the playback stops.
Is that perhaps the issue with the pitch bend? That it's simply converting the sample number(which monotonically increases) to a certain speed by just multiplying it with 'n'?
Should it actually instead add n to a sample counter instead to get a proper pitch bend (instead of multiplying the pitch speed with the sample number to get the effective sample)?
Edit: This is what's it's currently doing:
1 modulationratiocents += (Modulation*voice->modenv_pitchfactor); //Apply pitch bend as well! 2 //Apply pitch bend to the current factor too! 3 modulationratiocents += samplespeedup; //Speedup according to pitch bend! 4 5 //Apply the new modulation ratio, if needed! 6 if (modulationratiocents!=voice->modulationratiocents[filterindex]) //Different ratio? 7 { 8 voice->modulationratiocents[filterindex] = modulationratiocents; //Update the last ratio! 9 voice->modulationratiosamples[filterindex] = cents2samplesfactord((DOUBLE)modulationratiocents); //Calculate the pitch bend and modulation ratio to apply! 10 } 11 12 samplepos = (int_64)((DOUBLE)play_counter*voice->modulationratiosamples[filterindex]); //Apply the pitch bend and other modulation data to the sample to retrieve! 13 14 //Now, calculate the start offset to start looping! 15 samplepos += voice->startaddressoffset; //The start of the sample! 16 17 //First: apply looping! 18 loopflags = voice->currentloopflags; 19 if (voice->has_finallooppos && (play_counter >= voice->finallooppos)) //Executing final loop? 20 { 21 samplepos -= voice->finallooppos; //Take the relative offset to the start of the final loop! 22 samplepos += voice->finallooppos_playcounter; //Add the relative offset to the start of our data of the final loop! 23 } 24 else if (loopflags & 1) //Currently looping and active? 25 { 26 if (samplepos >= voice->endloopaddressoffset) //Past/at the end of the loop! 27 { 28 if ((loopflags & 0xC2) == 0x82) //We're depressed, depress action is allowed (not holding) and looping until depressed? 29 { 30 if (!voice->has_finallooppos) //No final loop position set yet? 31 { 32 voice->currentloopflags &= ~0x80; //Clear depress bit! 33 //Loop for the last time! 34 voice->finallooppos = samplepos; //Our new position for our final execution till the end! 35 voice->has_finallooppos = 1; //We have a final loop position set! 36 loopflags |= 0x20; //We're to update our final loop start! 37 } 38 } 39 40 //Loop according to loop data! 41 temp = voice->startloopaddressoffset; //The actual start of the loop! 42 //Loop the data! 43 samplepos -= temp; //Take the ammount past the start of the loop! 44 samplepos %= voice->loopsize; //Loop past startloop by endloop! 45 samplepos += temp; //The destination position within the loop! 46 //Check for depress special actions! 47 if (loopflags&0x20) //Extra information needed for the final loop? 48 { 49 voice->finallooppos_playcounter = samplepos; //The start position within the loop to use at this point in time! 50 } 51 } 52 }
Would that run without issues with pitch bends etc.(the samplespeedup variable)?
OK. Just changed the multiplying method of rendering samples to an additive method instead(just adding, taking remainders, keeping whole samples to render:
1 if (play_counter >= 0) //Valid to lookup the position? 2 { 3 modulationratiocents = 0; //Default: none! 4 if (chorus) //Chorus extension channel? 5 { 6 modulationratiocents = MIDIDEVICE_chorussinf(voice->chorussinpos[filterindex], chorus, 0); //Pitch bend default! 7 voice->chorussinpos[filterindex] += voice->chorussinposstep; //Step by one sample rendered! 8 if (voice->chorussinpos[filterindex] >= SINUSTABLE_PERCISION_FLT) voice->chorussinpos[filterindex] -= SINUSTABLE_PERCISION_FLT; //Wrap around when needed(once per second)! 9 } 10 11 modulationratiocents += (Modulation * voice->modenv_pitchfactor); //Apply pitch bend as well! 12 //Apply pitch bend to the current factor too! 13 modulationratiocents += samplespeedup; //Speedup according to pitch bend! 14 15 //Apply the new modulation ratio, if needed! 16 if (modulationratiocents != voice->modulationratiocents[filterindex]) //Different ratio? 17 { 18 voice->modulationratiocents[filterindex] = modulationratiocents; //Update the last ratio! 19 voice->modulationratiosamples[filterindex] = cents2samplesfactord((DOUBLE)modulationratiocents); //Calculate the pitch bend and modulation ratio to apply! 20 } 21 22 samplepos = voice->monotonecounter[filterindex]; //Monotone counter! 23 voice->monotonecounter_diff[filterindex] += (voice->modulationratiosamples[filterindex]); //Apply the pitch bend and other modulation data to the sample to retrieve! 24 if (voice->monotonecounter_diff[filterindex] >= 1.0f) //Valid to add for the next sample? 25 { 26 samplesskipped = (int_64)voice->monotonecounter_diff[filterindex]; //Load the samples skipped! 27 voice->monotonecounter_diff[filterindex] -= (float)samplesskipped; //Remainder! 28 voice->monotonecounter[filterindex] += samplesskipped; //Skipped this amount of samples ahead! 29 } 30 } 31 else 32 { 33 samplepos = 0; //Nothing to give on sample information! 34 }
That seems to have fixed the pitch bends to perform correctly 😁