Drawbar organ emulation


#1

Continuing discussion from https://sebiik.github.io/community.axoloti.com.backup/t/best-approach-for-additive-organ-patch/1638

http://electricdruid.net/technical-aspects-of-the-hammond-organ/
http://www.goodeveca.net/RotorOrgan/ToneWheelSpec.html

Say, we put a loops of every tonewheel oscillator into sdram, if we 'd use 128 oscillators, divided over 8MB sdram, that 'd be 64kB data per oscillator, or 32kSamples at 16bit.
If 48kHz samplerate is used for all oscillators, the loop length is 0.66 seconds, and all frequencies would need to be rounded to multiples of 1.5 Hz.That is not really long enough, the periodicity would be noticeable, and the frequency resolution not good enough for the bass tones. But the advantage is that interpolation is not needed, and when all the waves are interleaved in memory, transfer to SRAM can be done with DMA (12MB/s required, while the SDRAM bus allows 184MB/s bursts), the per-audio-sample workload is really multiplying each oscillator output with the corresponding amplitude and summing the results.

So with SDRAM we bump into a capacity limit while bandwidth is plenty. Some compromises are possible to reduce the size, like reducing the resolution to 8bit/sample, or reducing the samplerate. Samplerate could be reduced selectively, for instance running
- the top 16 oscillators at 48kHz samplerate
- the next 16 at 24KHz samplerate (1.33s loops)
- the next 16 at 12kHz (2.66s loops)
- the next 16 at 6kHz (5.33s loops)
- the remaining 64 at 3kHz (10.66s loops)
that adds a few interpolation filters to the workload, but this is not so much since this can be done after the tonewheel group summing.

Another option... sdcard streaming? Less bandwidth, but huge capacity...
Unlike current wave streaming objects, far larger buffers can be used, and only single buffers need to be read, containing all oscillator streams, interleaved...
Hmmmm that 'd also be useful in other contexts than drawbar organs.
For 128 streams at 48kHz/16bit the required bandwidth is 12228 kB/s, I haven't seen benchmark results that support this... Lowering the resolution to 48kHz/8bit requires 6114kB/s, close to what benchmarks with large buffer size indicate.
The quality reduction of 8bit may not be as bad as it looks, if proper dithering is used, this sort of samples does not need a significant dynamic range or any headroom. Still requires a bit too much bandwidth.
Reducing to 24kHz/8bit samplerate looks possible on paper, but that's a bit too aggressive, will lack presence in the sound.
Applying some other lightweight compression schemes could get it there, 4bit DPCM at 48kHz? Or reducing the oscillator count from 128 voices to 96 voices at 48kHz/8bit (4.6MB/s), benchmarks seem to support this bandwidth...

So how could it be partitioned in a patch and objects?
I think the 128 oscillators, vca's and summing should be one "monophonic" object, that accesses a table with the 128 amplitudes. The a polyphonic subpatch should add his weights to the amplitude table on note-on, remove his weights from the amplitude table on note-off...

Anyone interested to collaborate on this?


Best approach for additive 'organ' patch?
Analog from a tonewheel Hammond with drawbars
Hardware limits for heavy emulator
#2

Well this is certainly different from what I had in mind, but nonetheless interesting! I was definitely going for the 'polysynth' model with a small number of dynamically assigned voices.


#3

Keep in mind, there are hard limits to the approach I propose:

  • no pitch envelopes or pitch bends
  • no oscillator phase reset possible
  • tonewheels played by more than one voice will always be in-phase
  • the harmonics are not exact ratios for non-octave harmonics

With 9 drawbars, the total number of oscillators required for 10 voices is higher when using oscillators per-voice than global "tonewheels".

The advantage of the tonewheel approach is that it will lead to a more faithful drawbar organ sound than a polysynth model, and allow unlimited polyphony.


#4

Well, a lot of those limits apply to actual drawbar organs anyway!

Ah, but if you look at the other thread I've already managed to reduce the number of actual oscillators required per-voice! I could reduce it further if I could work out how to multiply by an arbitrary integer with overflow instead of saturation. After all, you can theoretically calculate an infinite number of sine harmonics from a single phasor. Obviously all those sine lookups aren't free, but it's still cheaper than 9 independent oscillators.


#5

I tried a tonewheel organ using a custom object.
tonewheels.axp (7.9 KB)
Currently uses 91 sine oscillators (computed, not the giga-table approach I outlined above).
The oscillators are tuned according to this table. Polyphony is "all you can eat", 84% load with simple chorus.

Potential future development:
Load could be reduced by halving the oscillator samplerate and then upsampling, highest tonewheel is below 6kHz, so that only compromises the clicking at note-on and note-off.
The sine wave oscillators may be further reduced by using a complex oscillator rather than table lookup.

Missing in the implementation is foldback and midi sustain pedal support.

Comments welcome!


#6

It works really well (quite impressed by the polyphony), I do find the clicking too loud though. Would it be possible to adjust it in any way ?


#7

Tonewheel organs have no envelope, only electrical switches that route the tonewheel oscillator outputs when a key is depressed. The clicks originate from the purely square shape of the envelope.
So I wonder how a real hammond would sound less "clicky". Every drawbar corresponds with a separate switch for every key, they do not close exactly simultaneously, spreading the clicks for every drawbar a bit over a small time. Those mechanical switches also bounce a bit when closing/opening, also adding character.
The rest of the signal path (vibrato, amp, speakers) is also likely to attenuate high frequencies, also softening the click sound. Pre-emphasis (increasing the output levels of the higher-pitched tonewheels), and lowpass filtering afterwards also reduces the click level in later Hammond organs.
http://www.dairiki.org/HammondWiki/KeyClick

One way to look at it: taking the Hammond as a golden reference, trying to squeeze the relevant characteristics into a digital model. The other way to look at it is taking maximum sonic advantage of the digital possibilities, only inspired by the tonewheel concept.

Before addressing the clicking, I think I'd need to reduce the oscillator load first...


#8

What version do I need to open this patch? I installed axoloti-win-1.0.6.msi because it is the latest release (and for some reason Axoloti "about" box says it's now 1.0.7). When I try to open the patch it says

Saving preferences...
preferences path : C:\Users\Jussi\Documents\axoloti\axoloti.prefs
org.simpleframework.xml.core.AttributeException: Attribute 'appVersion' does not have a match in class axoloti.PatchGUI at line 1
org.simpleframework.xml.core.AttributeException: Attribute 'appVersion' does not have a match in class axoloti.PatchGUI at line 1
at org.simpleframework.xml.core.Composite.readAttribute(Composite.java:494)
at org.simpleframework.xml.core.Composite.readAttributes(Composite.java:413)
at org.simpleframework.xml.core.Composite.access$300(Composite.java:59)
at org.simpleframework.xml.core.Composite$Builder.read(Composite.java:1382)
at org.simpleframework.xml.core.Composite.read(Composite.java:201)
at org.simpleframework.xml.core.Composite.read(Composite.java:148)
at org.simpleframework.xml.core.Traverser.read(Traverser.java:92)
at org.simpleframework.xml.core.Persister.read(Persister.java:625)
at org.simpleframework.xml.core.Persister.read(Persister.java:606)
at org.simpleframework.xml.core.Persister.read(Persister.java:584)
at org.simpleframework.xml.core.Persister.read(Persister.java:543)
at org.simpleframework.xml.core.Persister.read(Persister.java:521)
at org.simpleframework.xml.core.Persister.read(Persister.java:426)
at axoloti.MainFrame.OpenPatch(MainFrame.java:1203)
at axoloti.MainFrame.OpenPatch(MainFrame.java:1180)
at axoloti.MainFrame.jMenuOpenActionPerformed(MainFrame.java:836)
at axoloti.MainFrame.access$600(MainFrame.java:92)
at axoloti.MainFrame$9.actionPerformed(MainFrame.java:525)
at javax.swing.AbstractButton.fireActionPerformed(AbstractButton.java:2022)
at javax.swing.AbstractButton$Handler.actionPerformed(AbstractButton.java:2348)
at javax.swing.DefaultButtonModel.fireActionPerformed(DefaultButtonModel.java:402)
at javax.swing.DefaultButtonModel.setPressed(DefaultButtonModel.java:259)
at javax.swing.AbstractButton.doClick(AbstractButton.java:376)
at javax.swing.plaf.basic.BasicMenuItemUI.doClick(BasicMenuItemUI.java:833)
at javax.swing.plaf.basic.BasicMenuItemUI$Handler.menuDragMouseReleased(BasicMenuItemUI.java:943)
at javax.swing.JMenuItem.fireMenuDragMouseReleased(JMenuItem.java:586)
at javax.swing.JMenuItem.processMenuDragMouseEvent(JMenuItem.java:483)
at javax.swing.JMenuItem.processMouseEvent(JMenuItem.java:429)
at javax.swing.MenuSelectionManager.processMouseEvent(MenuSelectionManager.java:329)
at javax.swing.plaf.basic.BasicPopupMenuUI$MouseGrabber.eventDispatched(BasicPopupMenuUI.java:870)
at java.awt.Toolkit$SelectiveAWTEventListener.eventDispatched(Toolkit.java:2425)
at java.awt.Toolkit$ToolkitEventMulticaster.eventDispatched(Toolkit.java:2317)
at java.awt.Toolkit.notifyAWTEventListeners(Toolkit.java:2275)
at java.awt.Component.dispatchEventImpl(Component.java:4787)
at java.awt.Container.dispatchEventImpl(Container.java:2294)
at java.awt.Component.dispatchEvent(Component.java:4713)
at java.awt.LightweightDispatcher.retargetMouseEvent(Container.java:4888)
at java.awt.LightweightDispatcher.processMouseEvent(Container.java:4525)
at java.awt.LightweightDispatcher.dispatchEvent(Container.java:4466)
at java.awt.Container.dispatchEventImpl(Container.java:2280)
at java.awt.Window.dispatchEventImpl(Window.java:2750)
at java.awt.Component.dispatchEvent(Component.java:4713)
at java.awt.EventQueue.dispatchEventImpl(EventQueue.java:758)
at java.awt.EventQueue.access$500(EventQueue.java:97)
at java.awt.EventQueue$3.run(EventQueue.java:709)
at java.awt.EventQueue$3.run(EventQueue.java:703)
at java.security.AccessController.doPrivileged(Native Method)
at java.security.ProtectionDomain$JavaSecurityAccessImpl.doIntersectionPrivilege(ProtectionDomain.java:76)
at java.security.ProtectionDomain$JavaSecurityAccessImpl.doIntersectionPrivilege(ProtectionDomain.java:86)
at java.awt.EventQueue$4.run(EventQueue.java:731)
at java.awt.EventQueue$4.run(EventQueue.java:729)
at java.security.AccessController.doPrivileged(Native Method)
at java.security.ProtectionDomain$JavaSecurityAccessImpl.doIntersectionPrivilege(ProtectionDomain.java:76)
at java.awt.EventQueue.dispatchEvent(EventQueue.java:728)
at java.awt.EventDispatchThread.pumpOneEventForFilters(EventDispatchThread.java:201)
at java.awt.EventDispatchThread.pumpEventsForFilter(EventDispatchThread.java:116)
at java.awt.EventDispatchThread.pumpEventsForHierarchy(EventDispatchThread.java:105)
at java.awt.EventDispatchThread.pumpEvents(EventDispatchThread.java:101)
at java.awt.EventDispatchThread.pumpEvents(EventDispatchThread.java:93)
at java.awt.EventDispatchThread.run(EventDispatchThread.java:82)


#9

I use the latest release 1.0.9 - it works pretty well.


#10

1.0.9

if you open the patch in a text editor you will see the version of the app required in the first line

 <patch-1.0 appVersion="1.0.9">

generally, you can open with newer versions, but not older... and we enforce this from 1.0.10

with later versions of the app it will fail with a more meaningful message, I think I added this around 1.0.8... though would have to check the change logs to be sure.
its one of the reasons we may need to version the community library in the mid term.


#11

Hi All,

I'm not an Axoloti user, but I'm developing a clonewheel organ for a similar platform, STM32F4 Discovery. I've implemented the 91 tonewheels using recursive sine-cosine oscillator in integer math. Got this idea from the old Minsky's magic circles paper (HAKMEM). Precalculated coefficients (using >>15) provide frequency accuracy of about +-0.05Hz. Maybe you could benefit from my code?

Source: https://github.com/janifr/Syna
Some demos(I'm terrible palyer):https://www.youtube.com/watch?v=6I-yuUuVmNw
https://drive.google.com/file/d/0By_2CgAapME7cjRNclo5eGExVDA/view?usp=sharing

Jani


#12

Hi,

I started porting my code to axoloti. If anyone's interested here it is:
drawbar_organ_jf.axp (6.8 KB)


#13

New version. Implemented features at this point are upper foldback and harmonic percussion.
drawbar_organ_jf.axp (8.2 KB)


#14

hi, this is great, I wanted to try it but I couldn't figure out how to connect a midi/in object


#15

No need for separate midi/in object. It should work by just loadin the patch.


#16

Yes, my bad. I played for a while and it's quite good. Also the precussion sounds rather nice, but when I tried it with no drawbars pulled in it was obvious it needs more crisp and pescussive attack, also it would be great if you could also swich it off (like on original Hammond). It also doesn't eat too much cpu so it leaves room for a decent leslie simulation (chorus sounds ok for only a slow speed) Anyways thank you, this seems to be the best patch so far :slight_smile:


#17

Thank you for feedback. There's already an updated patch in Library->Commynity->janifr. Don't know if it's any better but at least a bit different. I'm going to keep on tweaking it to get closer a real B3. A big difficulty is I don't have access to a real one, so I have to compare my work against a clone, maybe VB3.


#18

Hi @janifr
Thanks for this new year gift :smiley:

I synched the libraries but i do not have a janifr directory.

I downloaded it from this thread and, it is really impressive.

Do you plan to implement a scanner chorus ?


#19

You're welcome.

I seem have problem with sync. Can anyone figure out what's wrong?

Sync Successful : factory (1.0.11,anon)
Sync Successful : community (1.0.11,janifr)
search path : /home/jani/axoloti/axoloti-factory/objects
search path : /home/jani/axoloti/objects
search path : /home/jani/axoloti/axoloti-contrib/objects
finished loading objects
Sync Successful : community (1.0.11,janifr)
Status: factory (1.0.11,anon) : OK ( 1.0.11,clean )
Status : home : OK
Status: community (1.0.11,janifr) : OK ( 1.0.11,clean )

The patch is located at ~/axoloti/axoloti-contrib/patches/janifr/drawbar_organ.axp

In fact I have a sort of a scanner in my earlier work. I'm going to port it here someday.


#20

Now my patch seems to be synced.