From the original SynthMaker forum (2004–2014), with its members’ names and dates.
Leslie147 ·
In case it's of use to others, earlier this eve I needed a volume control (i.e. attenuator) having the characteristics of an ideal 'log' potentiometer which, of course, provide an exponential law of attenuation vs. rotation. I've been reading Douglas Self's excellent books on audio.
I wanted: 0 dB at full rotation, -20 dB at half rotation, -40 dB at about 0.1 rotation and no output at zero rotation.
Writing the rotation as R, with values between 0 and 1, and defining a constant K, playing around in Excel I soon converged on the very simple formula: ratio = [(K^R)-1]/(K-1) and multiplying the audio stream by ratio provides the desired attenuation. K can be evaluated by solution of what resolves into a quadratic equation and the result for ratio = 0.1 (i.e. -20 dB) at for R = 0.5 is K = 9^2 = 81. Different laws can be obtained with different values of K but the limits remain at ratio = 1 for R = 1, and ratio = 0 for R = 0.
I wouldn't be surprised if others have trodden this path many times before, perhaps on this Forum.
Chris
infuzion ·
Cool, thanks for the tip!
MegaHurtz ·
sqrt (K)
Leslie147 ·
[quote="MegaHurtz"]sqrt (K)[/quote]
The quadratic is 0.1K - SQRT(K) + 0.9 = 0 ... the 0.1 comes from the requirement for 20 dB attenuation mid-scale and the square root comes from 0.5 rotation. Putting x^2 = K gives a conventional quadratic: 0.1x^2 - x + 0.9 = 0 for which the solutions are x = (1 +/- 0.8)/0.2 = 9 or 1. Disregarding the trivial solution, K = x^2 = 81.
MegaHurtz ·
You could change the range of (K) at the knob?
But ..
Im only saying this because it seems to be a lot of math for such a small end result.
Must be adding like one or two percent. While a knob that is just a good indicator does have the same feeling (or better). Maybe even a parabolic function?
cyto ·
Hi Leslie147,
I can tell from your user name that we have a lot in common 😉 .
Regarding the log taper: though I am a huge fan of finding simple quadratic approximations for otherwise intensive math, I don't think this is really a case where you need to do that. Afterall, if we are just rescaling an 0-1 volume knob to make it logarithmic, all the math can be done in "green" so why not just use the real exponential functions? Below is an example of how to do so that follows all of your requirements. I'd be remiss if I didn't point out that an "ideal" log taper will never actually go all the way down to 0 (or more appropriately -INF dbFS). This example let's you choose whether to have the actual low limit of the taper or simply 0. You can also change the range of the taper by indicating the desired value at half rotation. Settings are done in the "properties" panel. Enjoy.
-cyto
Leslie147 ·
Interesting, but surely much more complicated than what I posted ... with two Float powers, a Float equals and two Selectors. The solution of the quadratic is only needed to evaluate the constant K for a desired mid-sweep attenuation, then, as I posted originally, the green maths is just (K^R - 1)/(K - 1) where K is a constant and R the 0 to 1 rotation: one Float power and no selectors.
Chris
cyto ·
Leslie147 wrote: ...but surely much more complicated than what I posted
Not necessarily. The selectors are just there to switch the thing off at zero and could certainly be simplified with a "boolean and" (I threw this together kinda fast). Your method is an interesting take on the issue, and works out very easily if the "a" value is a nice round number like 0.1. However, I would imagine that finding the x-intercepts might get frustrating (and error prone) if you want a center value like, say, -48dBFS where the "a" coefficient doesn't round out as nicely. Not that it can't be done, but sometimes it's just easier to throw a number into the box and let the software take care of the rest. Especially if you're prototyping and want to change tapers quickly (and without having to break out the graphing calculator).
-cyto
infuzion ·
cyto wrote:
Leslie147 wrote: ...but surely much more complicated than what I posted
Not necessarily. The selectors are just there to switch the thing off at zero and could certainly be simplified with a "boolean and" (I threw this together kinda fast).
Off at zero to prevent denormals?
Will this boolean and version work?
martinvicanek ·
I think what the OP had in mind was a pseudo log (or rather pseudo exp) response curve as used for volume controls in the analog world. It resembles an ideal exponential except at small settings where it gently goes to zero (as opposed to the hard switch-off in the other examples). The effort to calculate K is very modest.
Leslie147 ·
Precisely.
martinvicanek ·
While at it, I thought it may be useful to have a general purpose rational mapper where you can specify the min, max, and center value to achieve the desired range and response. Feel free to use and improve!
Leslie147 ·
Amazing!
Another thing I've been pondering recently is pan-pot laws. Of course linear laws can be used, with -6 dB at mid travel and rather abrupt transitions to offness near the ends of travel, but I gather from the literature than the generally-accepted optimum is half way (in dB) between sin and sin^2 of the rotation, so sin^1.5 as a 'voltage' ratio, giving -4.5 dB at mid-travel. The two different cases correspond to normal un-corellated stereo and both channels combined into mono, respectively.
The complement is cos^1.5 (or sin with Pi/2 angle offset) but, my question is: how can one do most of the processing 'in the green', as it were, and then require only a single link into one's fast code? I haven't thought of a way yet - indeed, recently I did the sin and cos in green and used two separate links into code, where I only needed a pair of multiplications. I appreciate the laws don't need to be very accurate so a pair of really simple algorithms for sin^1.5 and cos^1.5 could allow all the calculation to be conducted in code with a single 0 to 1 input from the 'pot'.
Chris
cyto ·
infuzion wrote: Will this boolean and version work?
Yes, excellent!
martinvicanek wrote: While at it, I thought it may be useful to have a general purpose rational mapper where you can specify the min, max, and center value to achieve the desired range and response. Feel free to use and improve!
That is a handy little module. Awesome work (as always), Martin.
-cyto
martinvicanek ·
Leslie147 wrote: Another thing I've been pondering recently is pan-pot laws. [...]
Chris
Like this?
Leslie147 ·
[quote="martinvicanek"][quote="Leslie147"]
Another thing I've been pondering recently is pan-pot laws. [...]
Chris[/quote]
Like this?[/quote]
Yes, indeed, but I am seeking a way to reduce the number of linkages between the control (green) domain and the audio processing (code). As you've presented it, and as I reported before, there are two linkages for each audio signal.
Imagine an effects device with 5, or 8, or more, distinct channels, each with a pan control and possibly several other controls. To me at least, it seems unfortunate to have to devote twice that number of linkages to the pan function alone.
Chris
martinvicanek ·
Leslie147 wrote: there are two linkages for each audio signal
Well, one stream multiplication per output channel is not too bad. The only way I can think of reducing this number further is to pack it in Mono4. I updated my Pan.osm posted earlier with a packed version, however the overhead seems to offset the savings (the packed version actually uses more CPU).
Mo ·
Thanks for sharing,
martinvicanek wrote: The only way I can think of reducing this number further is to pack it in Mono4. I updated my Pan.osm posted earlier with a packed version, however the overhead seems to offset the savings (the packed version actually uses more CPU).
When using mono it would be faster to calculate in single channel ASM:
Leslie147 ·
Hmm ... three stream inputs for one audio signal. Isn't that even more expensive than two floats and one stream? What I am seeking is a way to do it with one stream input, the audio signal, and one input carrying a float value from green maths. As I noted before, what this requires is a simple, efficient, compact algorithm for sin^1.5 and its complement.
Chris
cyto ·
Leslie147 wrote: I am seeking is a way to do it with one stream input, the audio signal, and one input carrying a float value from green maths. As I noted before, what this requires is a simple, efficient, compact algorithm for sin^1.5 and its complement.s
Forgive me if I'm missing something, but doesn't that fact that you are using a "panner" imply that you will be using more than one audio stream? (ie Left/Right). Your desire seems to be reducing the number of "linkages". I'm not sure this is really an issue (unless your trying to clean up a schematic). I would much rather have a few extra code/ASM inputs (which really equate to a simple movaps) than the algorithm necessary to approximate the sin^1.5 reiterating at every sample (or even conservatively "hopped"). Maybe if you explained your circumstance it would help. I just can't imagine a situation where it would be beneficial to sacrifice CPU cycles for a simpler "looking" schematic.
-cyto
Leslie147 ·
'Forgive me if I'm missing something, but doesn't that fact that you are using a "panner" imply that you will be using more than one audio stream? (ie Left/Right).'
Of course, the action of panning interfaces a mono audio stream to a stereo pair of streams. Panning 8 mono input streams into a stereo pair is the practical example I noted before. 8 input streams, two output streams and then what? ... 8 or 16 additional stream inputs to control the pan functions for each of the inputs. I'm trying to avoid 16 in favour of 8.
'I would much rather have a few extra code/ASM inputs (which really equate to a simple movaps)'
Is that right? I can see that a hard coded value would involve no more than a movaps but doesn't each stream/mono input to a piece of code need to be polled each sample? I wonder how many additional clock cycles it takes to poll 8 additional stream/mono inputs. Perhaps the answer is an insignificant number. Perhaps stream inputs with green wires connected to them aren't polled each sample. I've not seen any of this documented but I'd welcome insight from others that know the answers.
Chris
cyto ·
Hey Chris,
Just for the sheer challenge of it, I put this thing together to see what something like you describe would entail. It's a simple 4(mono)in-stereo out little panning thingy. Basically what I did was write a little "stage 0" routine that builds a look-up table with the values of both the sin^1.5(x) and cos^1.5(x) curves (or close enough) . Then, in "stage 2" the code evaluates whether the panning on the four individual channels has changed. If so, then it fetches the value for that particular channel from the look-up table. If not, it just recalls the previously-stored value. Once the values are determined, it performs the mults, sums the left/right channels and spits out the stereo signal. Easy Peasy 😉
I chose only to include 128 values per curve to keep things simple and to have a nice, round 1kb buffer. Though depending on how well versed you are in assembly (probably more so than me), this could easily be changed. Because this requires a lot of assembly-specific operations, I could not write out a code equivalent but I tried to comment the ASM box as thoroughly as possible. Also I've included a little "demonstration" module that has pan pots and (dBFS) meters so you can see what's happening.
This is just kind of a sketch of one approach to your dilemma and I kind of threw it together rather quickly (so I'm sure there are mistakes), but it shows how you might go about approaching this. Because it only does the "hard" math once and has lots of "jumping" routines to avoid unnecessary calculations, it is still somewhat fast. Though I still maintain that Martin's work (and subsequently Mo's update) is a much better way to do this.
-cyto
trogluddite ·
Leslie147 wrote: Perhaps stream inputs with green wires connected to them aren't polled each sample. I've not seen any of this documented but I'd welcome insight from others that know the answers.
This is indeed the case - green and stream are processed on independent CPU threads, with the value passed at a relatively low rate.
As to when and how often the polling takes place - this is still a mystery; however it has been discovered that it is infrequent enough that changes in green values can very occasionally be missed. You can read more about this (and see some of our experiments) on the thread HERE.
So the general rule for keeping the CPU usage low is to do as much as possible using green maths - only using code/assembly where strictly necessary.
If the pan position is to be simply a GUI control, I would concur with previous posters that two streamins will be more efficient - however, should you ever wish to modulate the pan position with LFOs/Envelopes etc., an optimised 'all stream' solution would still be a very useful module to have.
BTW) Thanks to all for the interesting maths lessons! 🙂
Leslie147 ·
All: I think you are getting close to convincing me that pan controls require two control lines per input audio stream!
Cyto: thank you for taking up the challenge so seriously, and congratulations on an excellent piece of work. There's much I can learn from your code. For example, the last time I tried to manipulate ebx I was causing crashes but you appear to have it sorted. I'm a bit confused as to why you'd pack the four channels within the code and then process the channels individually with separate sections of code. I wonder if you took out the packing would the number of clock cycles used be any different?
The aspect that really surprised me is the algorithm you've used for (sin/cos)^1.5. My upbringing would have suggested 0.5[sinx+(sinx)^2], as you've used, would be a poor approximation but actually it's rather good! The correct form is the mean of the dB values which would be 0.5[20log10(sinx)+40log10(sinx)] = 30log10(sinx). However, in this application, the area of greatest interest is near the half-travel point where 30log10{sin(Pi/2)} = -4.5 dB and
0.5[sin(Pi/2)+{sin(Pi/2)}^2] = 0.60 and 20log10(0.6) = -4.44 dB !! Furthermore, plotting the ideal, your approximation and a few others using Excel gives the results plotted in the attachment
[img][/img]. The arithmetic average that you've used (orange curve) is closer to the ideal (dark blue curve) than the 'default' linear law (green), especially at half-travel, and, further out, is much closer than the 'ideal log volume control' law (red) I posted earlier and the result of adding a pull-up resistor to a linear pot (magenta). So thank you for that!
Trogluddite: thank you for the guidance, and the link, about the mysterious interface between green and stream. As soon as I started getting into coding I realised that there is more going on than is readily documented (e.g. when 'float val=0' actually retains the previously-attained value rather than doing what is typed). One thing I haven't yet fully understood is 'Stage 0'. I understand that code operating in Stage 0 runs one time when the SynthMaker model is started (e.g after a session of editing, when one clicks outside the code) but is there any controllable action that also makes Stage 0 code operate?
Chris
Leslie147 ·
Oops - nomenclature ambiguity! Where I have written 'code', I mean assembly language. I did find the 'RISC' code option helpful as a transition to assembly and a learning aid.
Chris
cyto ·
Leslie147 wrote: the last time I tried to manipulate ebx I was causing crashes but you appear to have it sorted.
I'm pretty sure that SM uses that register for its own internal processing, so I think when you add values to it, you end up overwriting some pretty important data. What I've found (after many crashes as well 🙂 ) is that you can use the ebx, but you first need to push its value onto the float stack and when you're done pop that value back into the ebx register where SM is expecting it. This method does require a little bit of attention as you need to make sure your process "pops" all of its temporary data off so that the original ebx value is at the end of the stack. Although thinking about it now, I would imagine you could probably just have a dedicated variable location to temporarily store the value as your process executes. There are probably people on this forum with more experience than me that could shed some more light on the subject.
Leslie147 wrote: I'm a bit confused as to why you'd pack the four channels within the code and then process the channels individually with separate sections of code. I wonder if you took out the packing would the number of clock cycles used be any different?
I'm not looking at the code right now, but I'm sure there was a method to my madness. IIRC, most of the "packing" is done in the stage 2 routines. In those routines, most of the processing is done on all four channels at sample rate. I think I only "split" them up in the individual fetching routines and for the summing at the end. Those fetching routines only happen when there is a change in the pan values. Because of the nature of the ram access, they must be done individually. But I tried to keep all of the operations that occur each sample packed in order to exploit the speediness of the SSE ops. I will admit also, that my experience with the FPU instructions is somewhat limited, so there may be unnecessary conversions that should just be chalked up to my naivete.
Leslie147 wrote: The aspect that really surprised me is the algorithm you've used for (sin/cos)^1.5. My upbringing would have suggested 0.5[sinx+(sinx)^2], as you've used, would be a poor approximation but actually it's rather good!
Yeah, I didn't want to get into the gory details of getting this too precise, as this was more an exercise of process for me. I only compared my approximation to the ideal on a linear scale and it looked "acceptable enough." I'm glad to see it holds up to a little bit of scrutiny.
Regarding the stage 0: it runs when a new voice is initiated. In mono, this means whenever the output is activated or whenever the stream gets "recompiled" (if that's the right word)- ie. when you alter the stream in SM or when you change position on "selectors." In "poly" sections, stage 0 executes at each new voice (at least theoretically it should). As far as I know there are no ways to "retrigger" it. But there are many workarounds to have code only executed under certain conditions. Even in my little example here, you can see that each pan value is perpetually stored in a holding variable. The code evaluates whether the current and the "held" value are the same, returning either a "00000[...]000" if they are different or a "11111[...]111" if they are the same. Once status is established, it uses a jnz operation to skip the sections of the code. You can use similar tests/evaluations to skip large sections of code. I have been using a lot of these little "jumping" routines lately and they can save a huge amount of cycles if used judiciously. In fact, I only recently had an "aha" moment when I realized that you can jump backwards to create little loops and such. I'm relatively new to this assembly stuff myself, but with each little "aha", I'm getting more comfortable with it. I very rarely even open up the "code" boxes any more.
-cyto
cyto ·
To follow up on the EBX topic...
I did a little bit of testing. And the ebx register does in fact carry a value. Actually the eax register is carrying a value as well, but the difference seems to be that the ebx value is a static value that does not ever seem to change while the program is running. The eax value, on the other hand, appears to be updating every sample. This probably explains why it is so much more forgiving if you stick your own value into it. I would imagine that SM is assigning a value to the ebx on initiation and is expecting it to stay there. When you wipe it out, the program crashes. I put together a quick little demonstration...
Interesting to note:
the ebx value seems to be dependent on the sound output settings. ie, If I use an ASIO device I get a certain static value, whereas if I use just the DS out I get a different static value.
the eax value seems to be dependent on the active voice. If you have a poly stream combined into the mono stream (like in the demo), the eax only fills up when a voice is active. If you remove the link between poly and mono, the eax fills up with a value as well.
Not sure anything can be gleaned from this except that it seems to confirm the theory that in order for you to be able to use the ebx register, you must replace its value when you are done.
-cyto
infuzion ·
I'm guessing:
EAX = sample counter
EBX = pointer to address
(though this is a bit OT)
Leslie147 ·
Thank you all for your comments - which I'm still processing!
Chris
lesobrod ·
Wow Very interesting and deep discussion! You guys should print scientifical paper 😉
I just think that almost all things that concern our sensorium have non-linear behavior.
E.g. well-known dependence "Pitch - Freq"
http://en.wikipedia.org/wiki/Mel_scale
I experimented a little with this osm and find out that our perception of pitch is a mystic thing...
Leslie147 ·
[quote=lesobrod]our perception of pitch is a mystic thing...[/quote]
I'm not sure about mystic; certainly non-linear, along with many other aspects of human life. It's always impressed me that if you wave a pencil back and forth and then, with your other hand (or with the help of an assistant) pull a strip of paper along beneath the pencil, in the appropriate direction, the result is fairly pure sine wave!
How do you get quotes to work here?
On the subject of pure sine waves, and in the hope that this will attract the attention of those 'in the know', I'm looking for advice on designing a sine-wave oscillator for a toneburst generator (for testing PPM ideas) that has good purity and can be switched quickly to different frequencies. I've seen postings around this Forum on compact sine-wave oscillators that exhibit low distortion but don't maintain constant amplitude after step-changes of frequency, and I've seen versions that use the 16-bit sin1 look-up table for which the amplitude is absolutley defined but the distortion is significant. I've tried the Gordon-Smith and Smith-Cook forms from Part III of Dattorro's well-known paper, with re-starts following frequency changes but their amplitudes don't seem sufficiently consistent.
I think I understand that the sine oscillator module provided by SynthMaker, like the other oscillators provided, uses interpolation to limit the bandwidth of the output signal by reducing its distortion, but the interpolation isn't explained.
I appreciate that I could just use the supplied module but I am more inclined to try to write something equivalent in assembly language. I wonder if linear interpolation between sin1 values from the table is the way to go, or whether low-pass filtering of the output of a non-interpolated oscillator is just as good (bearing in mind that one can tailor the cut-off frequency of the filter to the desired frequency of the sine wave). I expect their effects are equivalent in some conditions. I'd appreciate any advice from those who've explored low-distortion wavetable oscillators.
Chris
lesobrod ·
I'm not good in English so it takes a time to read your post 😊
What I've got now:
- About good Sin OSC try to look at this:
simple sine oscillator without using sin1()
- 'Quote' algorythm works so. When pressed the "Quote" button, all post turn to quote.
If you'd like isolate some text, write this literally:
[quote=" name " ] text [/quote]
Blue parameters are free and green (with "" and []) are predefined.
Leslie147 ·
[quote=" lesobrod " ]If you'd like isolate some text, write this literally ... etc. [/quote]
Thanks. Tried, as above, but I'm still missing something!
Yes, I was aware of the thread 'Re: simple sine oscillator without using sin1()' and I have seen the oscillator 'QuadOsc' posted by martinvicanek, the latest version of which does provide what I need. Thank you Martin. The delay in my action on this was from the fact that I'm still using Version 1.1.7 on this PC and some .osm files written using later versions won't open here - I have the recent 'Free' version on another PC.
Is there any reason why I shouldn't update the version on this PC? When 2.0 first came out I saw some postings about not being able to open .osm files created using earlier versions, and that put me off. But has that all been overcome now?
I'd still be interested to hear if anyone reading has experience of interpolating the sin1 table.
Chris
Wrote something here? To have a post removed or your name hidden, write to [email protected].