Synchro, a simple phase modulation synth
2-oscillator PM synth for creating nasty sounds.
⚠️ Todo
- [ ] Add non-automatable UI controls to change oversampling rate
- [ ] Use the HIIR library for proper bandlimiting
- [ ] Proper monophonic behavior. See #6516
- [ ] Glide/portamento
- [ ] Style knobs properly
❓ Optional
- [ ] Improve performance. Possible pain points:
- [ ] Heavy use of
exp()andpow()during waveform generation - [ ] The halves of the oversampling double buffer are not guaranteed to be contiguously allocated, which might cause cache issues.
- [ ] Heavy use of
- [ ] Use source
.svgfor background instead of.png, whenever that becomes possible
❌ Issues
- Line width for waveform preview is too thin. Ideally it would be 2px wide, but
gui::Graphdoesn't have any logic for line width - There's currently a bug where the modulator will reset its own phase every time the carrier completes a cycle, although the shape of the modulator in each of those windows is correct. For example, if the modulator is 1 octave below the carrier, it should complete a cycle once per every two carrier cycles, but instead it just plays the first half of its cycle twice.
At first glance you could add an argument in the constructor of the Graph class:
Graph::Graph( QWidget * _parent, graphStyle _style, int _width, int _height, bool readOnly=false )
In the mousePressEvent() you could have an if-statement and test this readOnly-bool.
I love it! It's so simple but you can make very heavy neuro basses with it!
Graph is a subclass of QWidget, so you can use QWidget's enabled property. Access it with isEnabled() and set it with setEnabled(bool).
Since you're no longer modifying the graph code, could you revert the formatting changes there?
I'm not quite sure of the advantage of having both "mod" and "mod max" controls. They're functionally identical, their values just being multiplied together. Is this a common thing to have? Can't you get the same result just by not turning "mod" up as high?
Yes, but it's harder. Compare it to pitch bend vs pitch bend range. It is slightly redundant, but it increases ease of use, especially in automation.
The "sync" control just seems to be a frequency multiplier. From what I've seen, sync usually means one oscillator resets another oscillator every cycle. Is the current behaviour and wording what is intended?
The current behavior is exactly as intended, but you're right; the wording is a bit off. I'm not sure what to call it exactly, but "sync" seemed closest to the functionality. I am open to suggestions here.
For clarification, the intended functionality is a frequency multiplier, but similar to sync, the phase is still reset every cycle of the base frequency. In addition, the amplitude decreases over time in agreement with the base frequency if the "chop" control is >0, regardless of this multiplier.
About sync, I have seen things labeled as "sync" work this way (e.g. Serum). You can think of it as the waveform being "synced" with a waveform with a frequency determined by the Sync knob. In other words, the Sync knob multiplies the frequency of a fake waveform which is causing the real waveform to reset. (EDIT: This is inaccurate, see my next post below...)
Yes, but it's harder. Compare it to pitch bend vs pitch bend range. It is slightly redundant, but it increases ease of use, especially in automation.
👍
The current behavior is exactly as intended, but you're right; the wording is a bit off. I'm not sure what to call it exactly, but "sync" seemed closest to the functionality. I am open to suggestions here.
OpulenZ and Watsyn call it "mul", with the tooltip "frequency multiplier". (At least I think those knobs are equivalent to this one, correct me if I'm wrong.)
About sync, I have seen things labeled as "sync" work this way (e.g. Serum). You can think of it as the waveform being "synced" with a waveform with a frequency determined by the Sync knob. In other words, the Sync knob multiplies the frequency of a fake waveform which is causing the real waveform to reset.
But it's not playing part of the waveform at normal speed then resetting it, it's playing the whole thing but faster. Plus, I recall you mentioning that some of the other labels on Serum are less than accurate. 😉
Sorry for all the messups in the previous posts, I'm super scatterbrained right now. I deleted some posts, now I will speak as if I actually have a brain. =)
The Sync knob is a frequency multiplier, but that waveform is being synced with a hypothetical waveform of the original frequency. So if you're playing a note at 440 Hz with a Sync value of 1.5, then the result is the waveform at 660 Hz being synced at 440 Hz. With integer Sync values, this is identical to pure frequency multiplication, but with non-integer values, it creates a drastically different sound.
I've seen this feature in multiple synthesizers, Serum's just the only one that came to mind. I think I recall some synthesizers from FL Studio also having that feature.
If you use a Sync value of 1.5 (or any other non-integer value), you should hear a drastically different timbre rather than just a frequency multiplication. Note that the copy of Synchro I have is from before this PR was made, let me know if this is no longer true...
It doesn't look to me like the modulator gets synced back to the original frequency any more. Here's the only place where the modulator's phase is modified, and it's just moved forward by the adjusted frequency (mod 2pi), without being reset: https://github.com/LMMS/lmms/blob/f8dfb32f2f6169bf5908b282047f5c8e10a8864b/plugins/Synchro/SynchroSynth.cpp#L86-L89 (I may well be wrong - I'm only getting this from the code. My last build of this PR is from before fractional sync was supported, so I can't test it right now.)
I think the while (sample_index[1] >= F_2PI) { sample_index[1] -= F_2PI; } part would cause the phase reset. It sets the phase to 0 (ish) whenever the original waveform (ignoring sync) reaches its end, which would interrupt the synced waveform when using fractional frequency multiplications.
The frequency multiplication happens in the waveform generation function, so the phase is automatically reset as part of that.
Ah yes, I see. I thought sample_index[1] was the phase after sync had been applied, but it's the phase before. Thanks for clearing that up.
@iansannar I fixed the merge conflict for you. Please pull it before you go further.
:robot: Hey, I'm @LmmsBot from github.com/lmms/bot and I made downloads for this pull request, click me to make them magically appear! :tophat:
Linux
- Linux (AppImage):
lmms-1.2.0.717-linux-x86_64.AppImage(build link)
Windows
- Windows 64-bit:
lmms-1.2.0.717-mingw-win64.exe(build link) - Windows 32-bit:
lmms-1.2.0.717-mingw-win32.exe(build link)
:robot:
{"platform_name_to_artifacts": {"Linux": [{"artifact": {"title": {"title": "(AppImage)", "platform_name": "Linux"}, "link": {"link": "https://302-204106384-gh.circle-artifacts.com/0/lmms-1.2.0.717-linux-x86_64.AppImage"}}, "build_link": "https://circleci.com/gh/iansannar/lmms/302?utm_campaign=vcs-integration-link&utm_medium=referral&utm_source=github-build-link"}], "Windows": [{"artifact": {"title": {"title": "64-bit", "platform_name": "Windows"}, "link": {"link": "https://303-204106384-gh.circle-artifacts.com/0/lmms-1.2.0.717-mingw-win64.exe"}}, "build_link": "https://circleci.com/gh/iansannar/lmms/303?utm_campaign=vcs-integration-link&utm_medium=referral&utm_source=github-build-link"}, {"artifact": {"title": {"title": "32-bit", "platform_name": "Windows"}, "link": {"link": "https://299-204106384-gh.circle-artifacts.com/0/lmms-1.2.0.717-mingw-win32.exe"}}, "build_link": "https://circleci.com/gh/iansannar/lmms/299?utm_campaign=vcs-integration-link&utm_medium=referral&utm_source=github-build-link"}]}, "commit_sha": "915c3a535cb69b58b34808c19e45f38592b50939"}
It seems like you removed the SynchroNote destructor accidentally. You should either restore it on the source file or add = default in the header file to fix the undefined reference error.
To prevent compiler warnings, you should make the order of declaration and initialization consistent.
@iansannar Do you want to add something more here? Or, can I start review?
@iansannar Do you want to add something more here? Or, can I start review?
It is usable in its current form, so you can go ahead and review it. I may add more features later, but that won't be for awhile as I am busy with school and work.
https://github.com/LMMS/lmms/issues/5592 ~~This PR needs review but is otherwise ready.~~
- [ ] Style Review: In progress
- [ ] Code Review: In progress
- [ ] Testing: ~~Tested by a handful of volunteers from the LMMS Discord server as well as @DouglasDGI's Discord server. No issues found so far.~~
Edit: None of this is ready.
@iansannar How is it going with sample-exactness? https://github.com/LMMS/lmms/pull/5147#discussion_r329351206
this is a pretty fun synth, i like :D
What's the status of this?
Pretty sure it's just waiting for @PhysSong to review both code and style.
Ah. I have no idea how I did that, but clearly I didn't rebase properly and now the commit history is duplicated 😓
Anyways, after re-examining this, it's clear that it's not even close to finished. I've updated the todo comment at the top.
I have resolved the merge conflicts by merging master and have fixed all compilation problems that have amassed over time with commit d67a3eda6f0.
Hope this might revive development. The synth still works. :slightly_smiling_face:
I didn't intend to delete this. I had mistakenly made these commits on my fork's master branch, so I decided to rename this branch feature/synchro, make a new master branch, and switch my fork's default branch to the new master. That somehow closed this PR.
I'm working on a full refactor of this synth anyways, so perhaps I'll just open a new PR with that once it's ready for review?
@rubiefawn Sure, you could open a new PR once it's ready if you'd like