Rewrote most of the synth to better support stereo signals and polyphony. VSTi removed as there is no plan to update the VSTi to support the new features.

The stereo opcode variants have bit 1 of the command stream set. The polyphony is split into two parts: 1) polyphony, meaning that voices reuse the same opcodes; 2) multitrack voices, meaning that a track triggers more than voice. They both can be flexible defined in any combinations: for example voice 1 and 2 can be triggered by track 1 and use instrument 1, and voice 3 by track 2/instrument 2 and voice 4 by track 3/instrument 2. This is achieved through the use of bitmasks: in the aforementioned example, bit 1 of su_voicetrack_bitmask would be set, meaning "the voice after voice #1 will be triggered by the same track". On the other hand, bits 1 and 3 of su_polyphony_bitmask would be set to indicate that "the voices after #1 and #3 will reuse the same instruments".
This commit is contained in:
Veikko Sariola 2020-05-16 08:25:52 +03:00
parent 5c1b87f254
commit 78d4cd50e8
238 changed files with 3460 additions and 21774 deletions

Binary file not shown.

Before

Width:  |  Height:  |  Size: 53 KiB

View File

@ -1,6 +1,7 @@
MIT License
Copyright (c) 2018 Dominik Ries
(c) 2020 Veikko Sariola
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal

188
README.md
View File

@ -1,87 +1,139 @@
# 4klang
Official 4klang repository
For discussion or feedback please use our #4klang channel on Slack:
[https://64klang.slack.com](https://join.slack.com/t/64klang/shared_invite/enQtNDM1NTY5OTE5Njk2LWRlM2NlZDUzNTdkNTZkYzEzMTA0NWJkMWM3NWI0ZTVlODhkM2FlNWFkMTk5YmE4ZGQ2ZWI1ZWQ4YjZjNjgxZDA)
# Sointu
A cross-platform modular software synthesizer for small intros, evolved from
[4klang](https://github.com/hzdgopher/4klang).
Summary
-------
4klang is a modular software synthesizer package intended to easily produce music for 4k intros (small executables with a maximum filesize of 4096 bytes containing realtime audio and visuals).
Sointu is work-in-progress. It is an evolution of [4klang](
https://github.com/hzdgopher/4klang), a modular software synthesizer intended
to easily produce music for 4k intros-small executables with a maximum
filesize of 4096 bytes containing realtime audio and visuals. Like 4klang, the
sound is produced by a virtual machine that executes small bytecode to
produce the audio; however, by now the internal virtual machine has been
heavily rewritten and extended to make the code more maintainable, possibly
even saving some bytes in the process.
It consists of a VSTi plugin example songs/instruments as well as an example C project showing how to include it in your code.
Or if you dare to compile it yourself also the source code for the synth core and VSTi plugin.
Project goals and current state
-------------------------------
The repository contains the folders:
- 4klang_VSTi (the precompiled plugin(s) and example instruments/songs)
- 4klang_source (the VSTi source as well as the needed 4klang.asm file for compilation in your exe)
The overly ambitious primary goals of the project:
- **Cross-platform support for win / mac / linux**. The build is already based
on CMake and compiles on Windows. Cross-platform YASM macros have been
drafted and remain to be tested. Once the project is more mature, I will
try compiling on other platforms.
- **Per instrument polyphonism**. An instrument should have the possibility to
have any number of voices. A draft of this has been written, but remains to
be tested. The maximum total number of voices will be 32: you can have 32
monophonic instruments or any combination of polyphonic instruments adding
up to 32.
- **Chords / more than one track per instrument**. For example, a polyphonic
instrument of 3 voices can be triggered by 3 parallel tracks, to produce
chords. A draft of this has been written, but remains to be tested.
- **Easily extensible**. Instead of %ifdef hell, the primary extension
mechanism will be through new opcodes for the virtual machine. Only the
opcodes actually used in a song are compiled into the virtual machine. The
goal is to try to write the code so that if two similar opcodes are used,
the common code in both is reused by moving it to a function. This opcode
extension mechanism is done.
- **Take the macro languge to its logical conclusion**. Only the patch
definition should be needed; all the %define USE_SOMETHING will be
defined automatically by the macros. Furthermore, only the opcodes needed
are compiled into the program. Done, see for example
[this test](tests/test_vco_trisaw.asm)! This has the nice implication that,
in future, there will be no need for binary format to save patches: the .asm
is easy enough to be imported into / exported from the GUI. Being a text
format, the .asm based patch definitions play nicely with source control.
- **Harmonized support for stereo signals**. Every opcode supports a stereo
variant: the stereo bit is hidden in the least significant bit of the
command stream and passed in carry to the opcode. This has several nice
advantages: 1) the opcodes that don't need any parameters do not need an
entire byte in the value stream to define whether it is stereo; 2) stereo
variants of opcodes can be implemented rather efficiently; in some cases,
the extra cost of stereo variant (e.g. most filters) is only 6 bytes. 3)
Since stereo opcodes usually follow stereo opcodes, and mono opcodes
follow mono opcodes, the stereo bits of the command bytes will be highly
correlated and if crinkler or any other compressor is doing its job, that
should make them highly predictable i.e. highly compressably. Mostly done.
- **Test-driven development**. Given that 4klang was already a mature project,
the first thing actually implemented was a set of regression tests to avoid
breaking everything beyond any hope of repair. Mostly done, using CTests.
Ttests for new opcodes / opcode variants implemented since 4klang are not
done.
- **Support for 64-bit targets**. Not started.
- **Browser-based GUI and MIDI instrument**. Modern browsers support WebMIDI,
WebAudio and, most importantly, they are cross-platform and come installed
on pretty much any computer. The only thing needed is to be able to
communicate with the platform specific synth; for this, the best
option seems to be to run the synth inside a tiny websocket server that
receives messages from browser and streams the audio to the browser.
Only the feasibility of the approach is proven (localhost websocket calls
have 1 ms range of latency), but nothing more is done yet.
The plugin project here is based on Visual Studio 2015, so that and above should compile out of the box.
The only additional thing you need for compilation is YASM/vsyasm, so download and follow the instructions here to get it running:
Possible, nice-to-have ideas:
- **Sample import from gm.dls**. This is Windows only, but implementing it
should be easy and the potential payoffs pretty high for Windows users, so
it is a nice prospect.
- **Tracker**. If the list of primary goals is ever exhausted, a browser-based
tracker would be nice to take advantage of all the features.
https://github.com/ShiftMediaProject/VSYASM
Anti-goals:
- **Ability to run Sointu as a DAW plugin (VSTi, AU, LADSPA and DSSI...)**.
None of these plugin technologies are cross-platform and they are full of
proprietary technologies. In particular, since Sointu was initiated after
Steinberg ceased to give out VSTi2 licenses, there is currently no legal or
easy way to compile it as a VSTi2 plugin. I downloaded the VSTi3 API and,
nope, sorry, I don't want to spend my time on it. And Renoise supports only
VSTi2... There is [JUCE](https://juce.com/), but it is again a mammoth and
requires apparently pretty deep integration in build system in the form of
Projucer. If someone comes up with a light-weight way and easily
maintainable way to make the project into DAW plugin, I may reconsider.
![4klang image](https://raw.githubusercontent.com/hzdgopher/4klang/master/4klang.png)
Design philosophy
-----------------
Examples
--------
- Try to avoid %ifdef hell as much as possible. If needed, try to include all
code toggled by a define in one block.
- Instead of prematurely adding %ifdef toggles to optimize away unused
features, start with the most advanced featureset and see if you can
implement it in a generalized way. For example, all the modulations are
now added into the values when they are converted from integers, in a
standardized way. This got rid of most of the %ifdefs in 4klang. Also, with
no %ifdefs cluttering the view, many opportunities to shave away
instructions became apparent. Also, by making the most advanced synth
cheaply available to the scene, we promote better music in future 4ks :)
- Size first, speed second. Speed will only considered, if the situation
becomes untolerable.
- Benchmark optimizations. Compression results are sometimes slightly
nonintuitive so alternative implementations should always be benchmarked
e.g. by compiling and linking a real-world song with [Leviathan](https://github.com/armak/Leviathan-2.0)
and observing how the optimizations
affect the byte size.
Some 4k intros using 4klang:
Background and history
----------------------
- http://www.pouet.net/prod.php?which=53937
- http://www.pouet.net/prod.php?which=68239
- http://www.pouet.net/prod.php?which=69642
- http://www.pouet.net/prod.php?which=69653
[4klang](https://github.com/hzdgopher/4klang) development was started in 2007
by Dominik Ries (gopher) and Paul Kraus (pOWL) of Alcatraz. The [write-up](
http://zine.bitfellas.org/article.php?zine=14&id=35) will still be helpful for
anyone looking to understand how 4klang and Sointu use the FPU stack to
manipulate the signals. Since then, 4klang has been used in countless of scene
productions and people use it even today.
Goal
----
Up to now the 4klang package was available only via http://4klang.untergrund.net
Lately various subversions of 4klang emerged and i felt it might be a good idea to have a public repo reflecting that.
So people can actively contribute, fix things i dont have time for or simply extend stuff.
Therefore the current branches available here are:
- 3.0.1 (as listed on http://4klang.untergrund.net)
- 3.11 (as listed on http://4klang.untergrund.net)
- master (~3.2, contains various fixes and a new unit type 'glitch' for a delay based retrigger effect)
History
-------
4klang development started in 2007 out of need and curiosity of how to write a tiny but flexible software synthesizer which can be used in 4k intros.
See this small writeup for more info
<br>http://zine.bitfellas.org/article.php?zine=14&id=35
However, 4klang is pretty deep in the [%ifdef hell](https://www.cqse.eu/en/blog/living-in-the-ifdef-hell/), and the polyphonism was
never implemented in a very well engineered way (you can have exactly 2
voices per instrument if you enable it). Also, reading through the code,
I spotted several avenues to squeeze away more bytes. These observations
triggered project Sointu. That, and I just wanted to learn x86 assembly, and
needed a real-world project to work on.
Credits
-------
4klang was developed by
<br>Dominik Ries (gopher) and Paul Kraus (pOWL) of Alcatraz.
The original 4klang was developed by Dominik Ries (gopher) and Paul Kraus
(pOWL) of Alcatraz.
Among the many sources of inspiration which lead to 4klang in its current state, here is a (probably not complete) list of the most influencial work by others:
Sointu was initiated by Veikko Sariola (pestis/bC!).
- <b>'Stoerfall Ost' by freestyle</b>
http://www.pouet.net/prod.php?which=743
<br>To my knowledge the first 4k intro using the fpu stack as 4klang does, creating the best 4k soundtrack of its time.
Conceptually i consider this to be the primary technological input as 4klang evolved around that idea of using the fpu stack.
So big respect and greetings to freestyle, particularly muhmac whom i also had some helpful discussions with during 4klang development.
- <b>V2 by kb of frabrausch</b>
http://www.pouet.net/prod.php?which=15073
<br>https://github.com/farbrausch/fr_public/tree/master/v2
Before 4klang i wrote my first synth as a V2 clone around 2004/2005 (as many people in the demoscene did actually).
Again way ahead of its time in terms of size/quality and in addition with some really helpful articles by kb this was a key to understanding what you need for synth development for a start:
<br>http://in4k.untergrund.net/various%20web%20articles/fr08snd1.htm
<br>http://in4k.untergrund.net/various%20web%20articles/fr08snd2.htm
<br>http://in4k.untergrund.net/various%20web%20articles/fr08snd3.htm
<br>http://in4k.untergrund.net/various%20web%20articles/fr08snd4.htm
- <b>http://www.musicdsp.org</b>
General source and for various synth module algorithms.
PoroCYon's [4klang fork](https://github.com/PoroCYon/4klang) inspired the macros
to better support cross-platform asm.

BIN
extern/vstgui.lib vendored

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -1,121 +0,0 @@
-----------------------------------------
first things first (info and legal stuff)
-----------------------------------------
4klang is a modular software synthesizer intended to be used in 4k intros.
you are free to use it in your 4k intros if you give proper credits.
the vsti plugin allows you to basically use any host application you like for composing a song. just put the 4klang.dll in the plugins folder or let some path point to its location.
please note 4klang is a 32bit vsti and needs a 32bit host to run.
4klang will output a c header file (.h) and the assembler source of the synth including the player and the song (.asm .inc) which you can compile to your executable, so including it is quite easy.
the examples are for demonstration purpose and not exactly a real world (aka 4k) scenario. especially they still link the 4klang.obj directly instead of compiling the 4klang.asm and 4klang.inc directly, but you should be able to figure that one out :)
i didnt include a seperate osx project as i dont have osx available, but i was reported the linux example should work without much hassle.
4klang is provided as is. reporting bugs/requests and general feedback is appreciated but changes depend on my time and mood :)
if you feel like sharing some intruments/songs you created with 4klang (after you used it), feel free to contact us (email at the bottom) we'd like to extend the examples.
since version 3.0 the source code package is available. so if you want to see the inner workings (assembler), the ugly vsti plugin code(c/c++) or simply really dare to compile it yourself or modify it fell free to do so now.
--------------
Using the VSTi
--------------
the basic principle behind the 4klang synthesizer is a signal stack.
each unit you add at a certain slot will work with the signals that were put on the stack before. there's no need to put the units directly after each other though, empty slots will just pass through the current signal stack.
available units are:
- envelope - a simple adsr envelope. puts a signal on the stack
- oscillator - the basic sound generating unit (with additional lfo option) puts a signal on the stack. it has a builtin shaping function whith behaves just like the distortion shaper below. also it now has a gate waveform which can be controlled via the bitpattern in the gui (osc signal will be 0 and 1 instead of -1 .. 1 with the other modes)
- filter - a multimode filter for sound shaping. modifies the topmost signal on the stack.
- distortion - a distortion unit. modifies the topmost signal on the stack.
- arithmetic - performs basic arithmetic operations like adding signals... also able to add the topmost signal again on the stack (push) or remove the topmost signal (pop), or exchange the two topmost signals (XCHG), or load the current midi note value as normalized float (0..1). just have a look at the explanation in the unit window.
- store - a storage command which routes the topmost signal to any unit in the local stack or to any other stack. this is your friend when it comes to modulations.
- panning - this unit will take the topmost signal (mono) and make a stereo signal out of it (including panning)
- delay/reverb - a delay line / reverb unit. it takes the topmost signal and modifies it. has bpm sync and note sync capability (use note sync for karplus-strong like plucked strings) as well as an integrated lfo for chorus/flanger effect.
- output - moves the stereo signal to the output buffers and optionally to the AUX buffer
- accumulate - this unit can only be used in the global stack. it will accumulate the output or aux buffers from all instruments.
- load - a unit which puts a value between -1 and 1 on the stack. also has a modulation capability so any store command can put its signal now directly on the stack.
the default instrument should give you a hint how a instrument should be built up.
if you want to have an audiable instrument you basically should not touch the last three units in the instrument stack (panning, delay/reberb, output). you can remove the delay/reverb though. in any case the signal count for an instrument MUST be 0 after the last unit.
if you use modulations, make sure that if they are generated with an own envelope or oscillator, that you remove the signal from the stack afterwards (via arithmetic->pop).
in the end its totally depending on what you want to do. e.g you could define an instrument which is not producing any sound itself but rather modulates some unit in some other instrument, in that case it will be a controll instrument. and since controll instruments dont need an output (since the signal is routed via the store unit) you dont need the panning, delay/reverb and output at the end. you only have to make sure you removed all signals from the stack after you stored the modulation signal somewhere. controll instruments are a bit tricky but mighty, since they give you the option to modulate a sound via triggering the controll instrument in its pattern.
the global stack behaves essentially just like an instrument stack, so you are free to use any stacking combination you like. just make sure that there's at least an accumulator for the output or the aux buffers somewhere (like in the default setting). the signal count in the global stack MUST be 0 after the last unit (output).
on the lower right you have the panic button, so in a case where you fucked up the sound/signal try pressing it :)
also in that corner is the polyphony combo box. basically each instrument in 4klang is monophone, but when setting the polyphony to 2 it allows you to have 1 note beeing in release mode (fading out) while the new one is playing already. the default setting of 1 should be quite ok. only set to 2 if you really need it (it doubles cpu usage, both at composing time as well as in the intro)
please note that patches and instruments have to be loaded/saved manually. so if you load e.g a renoise song the VSTI plugin will show up but will only contain the default patch, therefore you have to load the corresponding patch for the song yourself.
4klang makes a backup of the current patch every 60 seconds. the backup file will be stored to c:\4klang.4kp. this file is also written when 4klang is shutdown by the host application. so in case you forgot to store your patch you have a chance it's not all lost :)
--------------------------------------
recording a song for use in a 4k intro
--------------------------------------
recording a song is quite easy. first make sure you have rewinded your song and that nothing is still playing (press panic to be sure).
then press the record button, afterwards start the song. when the song finished press stop and choose your destination path for the compiled .obj file.
for info on how to include the object file in your 4k take a look at the example code.
the options in the record section allow you to use different synth internal pattern sizes, different quantization, clipping of the final output, denormalization (preventing severe slowdown while processing) and enable the envelope and note recording option in the synth.
Additionally you may want the synth to output standard 16bit integer samples instead of floating point samples. Finally you have the option to let the object file be compiled in the linux ELF or OSX MACHO format (obviously only if you want to use it with a linux/osx binary).
envelope and note recording will be done when the sound is calculated in the intro and will give you access to those buffer so you can sync to the envelope level and current note of each instrument. for info on how to use that also refer to the example code.
PLEASE NOTE:
you need to have your VSTi host running at 44100hz, otherwise your recordings will be fucked up, since the synth will work ONLY at 44100hz in the intro
also any kind of effects like volume changes, ... and groove settings from the host wont be there in the actual 4k. so make sure you only hit notes and note offs while composing
-----------------------------
workflow and compression tips
-----------------------------
version 2.7 introduces a new file format, so instruments and patches created with it wont work in previous versions anymore (no need to keep an older 4klang.dll though).
but the 2.7 version will automatically convert older files to the current format. you'll be notified and advised to save it again with the current version when that happens.
version 3.0 also introduces a new file format to enable the new load unit. autoconvert again happens automagically
in fact without using the new load unit when saving the format is identical to the previous version except for the header bytes which are now '4k13' instead of '4k12'. so if you really need it in the previous version again a hexeditor is your friend.
you can load and save and reset the whole patch, the current selected instrument, and even a single unit. so a good way to work is creating a set of instruments / unit settings which can be reused often.
also you can load instruments and patches (4ki and 4kp) via drag&drop into the 4klang gui.
when building instruments always have an eye on the signal count (numbers on the right side) and the last line in the instrument/global stack. if the signal stack is invalid (which means a unit doesnt have the correct number of inputs) it tells you at which unit that error occured.
only use units and as many units you really need. 4klang is really compact, but using 32 units per instrument will most probably give quite big files in the end.
the default values for the parameters usually are set to give the minumum code size (meaning they skip functionality), only exception here is the damping parameter in the delay line. setting that to 0 instead will lead to smaller object in the end.
same with the patch and song data. the more repetitive parameter values and the song itself are the smaller your file will be in the end.
apart from that, try understanding what each unit effectively does before doing really fancy stuff with ist :) start simple and get more complex over time (especially when doing cross modulations from one instrument to another).
-----------------------------
Info for version 3.11
-----------------------------
this is a more or less experimental version mainly for use in 8k context.
version 3.11 also introduces a new file format to enable the new features. autoconvert again should happen automagically
biggest change is the existence of a second plugin called 8klang. it basically is a second 4klang plugin just that ich can be used to have additional 16 instruments.
you can only have modulations across channels within each plugin (so not across the plugins).
when you record a song with both plugins active, be sure to stop recording on the 4klang plugin first, then on the 8klang plugin.
the 8klang plugin will then try to merge things (thats the experimental part, it did work for us in 8ks so far, but you never know :))
also a couple of new features are available:
- extension to 64 slots for each instrument.
- stereo flags for some nodes where possible (oscillator, filter, distortion)
- some additions to the arithmetic unit
- instrument link option, so when you have the same instrument loaded in several channels for e.g. playing chords you can now change one instrument and the others will apply the same changes.
a nice tutorial for some of those features and sound design in general can be found here (thank you wayfinder):
https://www.youtube.com/watch?v=wP__g_9FT4M
for questions, bug reports, rants ... mail to: atz4klangATuntergrundDOTnet
or catch us on IRC (#atz on IRCNet)
updates will be available via:
- http://4klang.untergrund.net
- http://www.pouet.net/prod.php?which=53398
have fun!
(c) gopher&powl
alcatraz 2009

File diff suppressed because it is too large Load Diff

View File

@ -1,586 +0,0 @@
%ifndef _4KLANG_INC
%define _4KLANG_INC
; Following defines have to be defined before this include:
; MAX_INSTRUMENTS e.g. %define MAX_INSTRUMENTS 10
; BPM e.g. %define BPM 100
; MAX_PATTERNS e.g. %define MAX_PATTERNS 1
;
; Optionally:
; PATTERN_SIZE_SHIFT e.g. %define PATTERN_SIZE_SHIFT 4 <- this is the default
%macro EXPORT 1
global %1
%1
%endmacro
%ifidn __OUTPUT_FORMAT__,win32
; on win32, function f with n parameters is mangled as "_f@n"
%define MANGLE_FUNC(f,n) _ %+ f %+ @ %+ n
%define WIN_OR_MAC
%endif
%ifidn __OUTPUT_FORMAT__,elf32
; on linux, function f with n parameters is mangled as "f"
%define MANGLE_FUNC(f,n) f
%endif
%ifidn __OUTPUT_FORMAT__,macho32
; on mac, function f with x parameters is mangled as "_f"
%define MANGLE_FUNC(f,n) _f
%define WIN_OR_MAC
%endif
%ifdef WIN_OR_MAC
; Windows has crinkler so one may USE_SECTIONS to put everything in custom sections to aid crinkler.
; Maybe mac users need it too
%ifdef USE_SECTIONS
%define SECT_BSS(n) section . %+ n bss align=1
%define SECT_DATA(n) section . %+ n data align=1
%define SECT_TEXT(n) section . %+ n code align=1
%else
%define SECT_BSS(n) section .bss align=1
%define SECT_DATA(n) section .data align=1
%define SECT_TEXT(n) section .code align=1
%endif
; On windows and mac, data label d is mangled as "_d"
%define MANGLE_DATA(d) _ %+ d
%else
; Linux
%ifdef USE_SECTIONS
%define SECT_BSS(n) section .bss. %+ n nobits alloc noexec write align=1
%define SECT_DATA(n) section .data. %+ n progbits alloc noexec write align=1
%define SECT_TEXT(n) section .text. %+ n progbits alloc exec nowrite align=1
%else
%define SECT_BSS(n) section .bss. nobits alloc noexec write align=1
%define SECT_DATA(n) section .data. progbits alloc noexec write align=1
%define SECT_TEXT(n) section .text. progbits alloc exec nowrite align=1
%endif
; On linux, data label d is mangled as "d"
%define MANGLE_DATA(d) d
%endif
%ifdef GO4K_USE_ALL
; GO4K_USE_ALL is convenience way to enable almost all features of the synth, for vsti plugins and such which
; do not have any size constraints. However, GO4K_USE_ALL should only enable features that absolutely do not
; change the functioning of the synth in any way, just add features. Clipping, 16 bit output etc. should still
; be enabled only whent they are actually needed
; Things that are NOT defined by GO4K_USE_ALL
;%define GO4K_USE_16BIT_OUTPUT ; // removing this will output to 32bit floating point buffer
;%define GO4K_USE_GROOVE_PATTERN ; // removing this skips groove pattern code
;%define GO4K_USE_ENVELOPE_RECORDINGS ; // removing this skips envelope recording code
;%define GO4K_USE_NOTE_RECORDINGS ; // removing this skips note recording code
;%define GO4K_USE_UNDENORMALIZE ; // removing this skips denormalization code in the units
;%define GO4K_CLIP_OUTPUT ; // removing this skips clipping code for the final output
%define GO4K_USE_DST ; // removing this will skip DST unit
%define GO4K_USE_DLL ; // removing this will skip DLL unit
%define GO4K_USE_PAN ; // removing this will skip PAN unit
%define GO4K_USE_GLOBAL_DLL ; // removing this will skip global dll processing
%define GO4K_USE_FSTG ; // removing this will skip global store unit
%define GO4K_USE_FLD ; // removing this will skip float load unit
%define GO4K_USE_GLITCH ; // removing this will skip GLITCH unit
%define GO4K_USE_ENV_CHECK ; // removing this skips checks if processing is needed
%define GO4K_USE_VCO_CHECK ; // removing this skips checks if processing is needed
%define GO4K_USE_VCO_PHASE_OFFSET ; // removing this will skip initial phase offset code
%define GO4K_USE_VCO_SHAPE ; // removing this skips waveshaping code
%define GO4K_USE_VCO_GATE ; // removing this skips gate code
%define GO4K_USE_VCO_MOD_FM ; // removing this skips frequency modulation code
%define GO4K_USE_VCO_MOD_DM ; // removing this skips detune modulation code
%define GO4K_USE_VCO_STEREO ; // removing this skips stereo code
%define GO4K_USE_VCF_CHECK ; // removing this skips checks if processing is needed
%define GO4K_USE_VCF_HIGH ; // removing this skips code for high output
%define GO4K_USE_VCF_BAND ; // removing this skips code for band output
%define GO4K_USE_VCF_PEAK ; // removing this skips code for peak output
%define GO4K_USE_VCF_STEREO ; // removing this skips code for stereo filter output
%define GO4K_USE_DST_CHECK ; // removing this skips checks if processing is needed
%define GO4K_USE_DST_SH ; // removing this skips sample and hold code
%define GO4K_USE_DST_STEREO ; // removing this skips stereo processing
%define GO4K_USE_DLL_NOTE_SYNC ; // removing this will skip delay length adjusting code (karplus strong)
%define GO4K_USE_DLL_CHORUS ; // removing this will skip delay chorus/flanger code
%define GO4K_USE_DLL_CHORUS_CLAMP ; // removing this will skip chorus lfo phase clamping
%define GO4K_USE_DLL_DAMP ; // removing this will skip dll damping code
%define GO4K_USE_DLL_DC_FILTER ; // removing this will skip dll dc offset removal code
%define GO4K_USE_FSTG_CHECK ; // removing this skips checks if processing is needed
%define GO4K_USE_WAVESHAPER_CLIP ; // removing this will skip clipping code
%endif
%ifdef GO4K_USE_VCO_SHAPE
%define INCLUDE_WAVESHAPER
%endif
%ifdef GO4K_USE_DST
%define INCLUDE_WAVESHAPER
%endif
%ifdef GO4K_USE_ENVELOPE_RECORDINGS
%define GO4K_USE_BUFFER_RECORDINGS
%endif
%ifdef GO4K_USE_NOTE_RECORDINGS
%define GO4K_USE_BUFFER_RECORDINGS
%endif
; //----------------------------------------------------------------------------------------
; // synth defines
; //----------------------------------------------------------------------------------------
%define MAX_DELAY 65536
%define MAX_UNITS 64
%define MAX_UNIT_SLOTS 16
%define MAX_WORK_VARS 8
%ifndef SAMPLE_RATE
%define SAMPLE_RATE 44100
%endif
%ifndef MAX_VOICES
%define MAX_VOICES 1
%endif
%ifndef HLD
%define HLD 1
%endif
%ifndef PATTERN_SIZE_SHIFT
%define PATTERN_SIZE_SHIFT 4
%endif
%define PATTERN_SIZE (1 << PATTERN_SIZE_SHIFT)
%define MAX_TICKS (MAX_PATTERNS*PATTERN_SIZE)
%define SAMPLES_PER_TICK (SAMPLE_RATE*4*60/(BPM*16))
%define DEF_LFO_NORMALIZE 0.000038
%define MAX_SAMPLES (SAMPLES_PER_TICK*MAX_TICKS)
%define GO4K_BEGIN_CMDDEF(def_name)
%define GO4K_END_CMDDEF db 0
%define GO4K_BEGIN_PARAMDEF(def_name)
%define GO4K_END_PARAMDEF
; //----------------------------------------------------------------------------------------
; // ENV structs
; //----------------------------------------------------------------------------------------
GO4K_ENV_ID equ 1
%macro GO4K_ENV 5
db %1
db %2
db %3
db %4
db %5
%endmacro
%define ATTAC(val) val
%define DECAY(val) val
%define SUSTAIN(val) val
%define RELEASE(val) val
%define GAIN(val) val
struc go4kENV_val
;// unit paramters
.attac resd 1
.decay resd 1
.sustain resd 1
.release resd 1
.gain resd 1
.size
endstruc
struc go4kENV_wrk
;// work variables
.state resd 1
.level resd 1
.size
endstruc
%define ENV_STATE_ATTAC 0
%define ENV_STATE_DECAY 1
%define ENV_STATE_SUSTAIN 2
%define ENV_STATE_RELEASE 3
%define ENV_STATE_OFF 4
; //----------------------------------------------------------------------------------------
; // VCO structs
; //----------------------------------------------------------------------------------------
GO4K_VCO_ID equ 2
%macro GO4K_VCO 8
db %1
db %2
%ifdef GO4K_USE_VCO_PHASE_OFFSET
db %3
%endif
%ifdef GO4K_USE_VCO_GATE
db %4
%endif
db %5
%ifdef GO4K_USE_VCO_SHAPE
db %6
%endif
db %7
db %8
%endmacro
%define TRANSPOSE(val) val
%define DETUNE(val) val
%define PHASE(val) val
%define GATES(val) val
%define COLOR(val) val
%define SHAPE(val) val
%define FLAGS(val) val
%define SINE 0x01
%define TRISAW 0x02
%define PULSE 0x04
%define NOISE 0x08
%define LFO 0x10
%define GATE 0x20
%define VCO_STEREO 0x40
struc go4kVCO_val
;// unit paramters
.transpose resd 1
.detune resd 1
%ifdef GO4K_USE_VCO_PHASE_OFFSET
.phaseofs resd 1
%endif
%ifdef GO4K_USE_VCO_GATE
.gate resd 1
%endif
.color resd 1
%ifdef GO4K_USE_VCO_SHAPE
.shape resd 1
%endif
.gain resd 1
.flags resd 1
.size
endstruc
struc go4kVCO_wrk
;// work variables
.phase resd 1
;// stero variables
.phase2 resd 1
.gatestate resd 1
.detune_mod resd 1
.freq_mod resd 1
.size
endstruc
; //----------------------------------------------------------------------------------------
; // VCF structs
; //----------------------------------------------------------------------------------------
GO4K_VCF_ID equ 3
%macro GO4K_VCF 3
db %1
db %2
db %3
%endmacro
%define LOWPASS 0x1
%define HIGHPASS 0x2
%define BANDPASS 0x4
%define BANDSTOP 0x3
%define ALLPASS 0x7
%define PEAK 0x8
%define STEREO 0x10
%define FREQUENCY(val) val
%define RESONANCE(val) val
%define VCFTYPE(val) val
struc go4kVCF_val
;// unit paramters
.freq resd 1
.res resd 1
.type resd 1
.size
endstruc
struc go4kVCF_wrk
;// work variables
.low resd 1
.high resd 1
.band resd 1
.freq_mod resd 1
;// stereo variables
.low2 resd 1
.high2 resd 1
.band2 resd 1
.size
endstruc
; //----------------------------------------------------------------------------------------
; // DST structs
; //----------------------------------------------------------------------------------------
GO4K_DST_ID equ 4
%macro GO4K_DST 3
db %1
%ifdef GO4K_USE_DST_SH
db %2
%endif
%ifdef GO4K_USE_DST_STEREO
db %3
%endif
%endmacro
%define DRIVE(val) val
%define SNHFREQ(val) val
%define FLAGS(val) val
struc go4kDST_val
;// unit paramters
.drive resd 1
%ifdef GO4K_USE_DST_SH
.snhfreq resd 1
%endif
%ifdef GO4K_USE_DST_STEREO
.flags resd 1
%endif
.size
endstruc
struc go4kDST_wrk
;// work variables
.out resd 1
.snhphase resd 1
;// stereo variables
.out2 resd 1
.size
endstruc
; //----------------------------------------------------------------------------------------
; // DLL structs
; //----------------------------------------------------------------------------------------
GO4K_DLL_ID equ 5
%macro GO4K_DLL 8
db %1
db %2
db %3
%ifdef GO4K_USE_DLL_DAMP
db %4
%endif
%ifdef GO4K_USE_DLL_CHORUS
db %5
db %6
%endif
db %7
db %8
%endmacro
%define PREGAIN(val) val
%define DRY(val) val
%define FEEDBACK(val) val
%define DEPTH(val) val
%define DAMP(val) val
%define DELAY(val) val
%define COUNT(val) val
struc go4kDLL_val
;// unit paramters
.pregain resd 1
.dry resd 1
.feedback resd 1
%ifdef GO4K_USE_DLL_DAMP
.damp resd 1
%endif
%ifdef GO4K_USE_DLL_CHORUS
.freq resd 1
.depth resd 1
%endif
.delay resd 1
.count resd 1
.size
endstruc
struc go4kDLL_wrk
;// work variables
.index resd 1
.store resd 1
.dcin resd 1
.dcout resd 1
.phase resd 1
;// the delay buffer
.buffer resd MAX_DELAY
.size
endstruc
; //----------------------------------------------------------------------------------------
; // FOP structs
; //----------------------------------------------------------------------------------------
GO4K_FOP_ID equ 6
%macro GO4K_FOP 1
db %1
%endmacro
%define OP(val) val
%define FOP_POP 0x1
%define FOP_ADDP 0x2
%define FOP_MULP 0x3
%define FOP_PUSH 0x4
%define FOP_XCH 0x5
%define FOP_ADD 0x6
%define FOP_MUL 0x7
%define FOP_ADDP2 0x8
%define FOP_LOADNOTE 0x9
%define FOP_MULP2 0xa
struc go4kFOP_val
.flags resd 1
.size
endstruc
struc go4kFOP_wrk
.size
endstruc
; //----------------------------------------------------------------------------------------
; // FST structs
; //----------------------------------------------------------------------------------------
GO4K_FST_ID equ 7
%macro GO4K_FST 2
db %1
dw %2
%endmacro
%define AMOUNT(val) val
%define VALUE_MOD(unit,unittype,slot,flags) unit*MAX_UNIT_SLOTS+go4k %+ unittype %+ _val. %+ slot /4+MAX_WORK_VARS+flags
%define WRK_MOD(unit,unittype,slot,flags) unit*MAX_UNIT_SLOTS+go4k %+ unittype %+ _wrk. %+ slot /4+flags
%define FST_SET 0x0000
%define FST_ADD 0x4000
%define FST_POP 0x8000
struc go4kFST_val
.amount resd 1
.size
endstruc
struc go4kFST_wrk
.size
endstruc
; //----------------------------------------------------------------------------------------
; // PAN structs
; //----------------------------------------------------------------------------------------
GO4K_PAN_ID equ 8
%macro GO4K_PAN 1
%ifdef GO4K_USE_PAN
db %1
%endif
%endmacro
%define PANNING(val) val
struc go4kPAN_val
%ifdef GO4K_USE_PAN
.panning resd 1
%endif
.size
endstruc
struc go4kPAN_wrk
.size
endstruc
; //----------------------------------------------------------------------------------------
; // OUT structs
; //----------------------------------------------------------------------------------------
GO4K_OUT_ID equ 9
%macro GO4K_OUT 2
db %1
%ifdef GO4K_USE_GLOBAL_DLL
db %2
%endif
%endmacro
%define AUXSEND(val) val
struc go4kOUT_val
.gain resd 1
%ifdef GO4K_USE_GLOBAL_DLL
.auxsend resd 1
%endif
.size
endstruc
struc go4kOUT_wrk
.size
endstruc
; //----------------------------------------------------------------------------------------
; // ACC structs (this is for the synth def only)
; //----------------------------------------------------------------------------------------
GO4K_ACC_ID equ 10
%macro GO4K_ACC 1
db %1
%endmacro
%define OUTPUT 0
%define AUX 8
%define ACCTYPE(val) val
struc go4kACC_val
.acctype resd 1
.size
endstruc
struc go4kACC_wrk
.size
endstruc
%ifdef GO4K_USE_FLD
; //----------------------------------------------------------------------------------------
; // FLD structs
; //----------------------------------------------------------------------------------------
GO4K_FLD_ID equ 11
%macro GO4K_FLD 1
db %1
%endmacro
%define VALUE(val) val
struc go4kFLD_val
.value resd 1
.size
endstruc
struc go4kFLD_wrk
.size
endstruc
%endif
%ifdef GO4K_USE_GLITCH
; //----------------------------------------------------------------------------------------
; // GLITCH structs
; //----------------------------------------------------------------------------------------
GO4K_GLITCH_ID equ 12
%macro GO4K_GLITCH 5
db %1
db %2
db %3
db %4
db %5
%endmacro
%define ACTIVE(val) val
%define SLICEFACTOR(val)val
%define PITCHFACTOR(val)val
%define SLICESIZE(val) val
struc go4kGLITCH_val
;// unit paramters
.active resd 1
.dry resd 1
.dsize resd 1
.dpitch resd 1
.slicesize resd 1
.size
endstruc
struc go4kGLITCH_wrk
;// work variables
.index resd 1
.store resd 1
.slizesize resd 1
.slicepitch resd 1
.unused resd 1
;// the delay buffer
.buffer resd MAX_DELAY
.size
endstruc
%endif
%ifdef GO4K_USE_FSTG
; //----------------------------------------------------------------------------------------
; // FSTG structs
; //----------------------------------------------------------------------------------------
%ifdef GO4K_USE_GLITCH
GO4K_FSTG_ID equ 13
%else
GO4K_FSTG_ID equ 12
%endif
%macro GO4K_FSTG 2
db %1
dw %2
%endmacro
%define GLOBAL_VALUE_MOD(inst,unit,unittype,slot,flags) inst*go4k_instrument.size*MAX_VOICES/4 + unit*MAX_UNIT_SLOTS+go4k %+ unittype %+ _val. %+ slot /4+(go4k_instrument.workspace/4)+MAX_WORK_VARS+flags
%define GLOBAL_WRK_MOD(inst,unit,unittype,slot,flags) inst*go4k_instrument.size*MAX_VOICES/4 + unit*MAX_UNIT_SLOTS+go4k %+ unittype %+ _wrk. %+ slot /4+(go4k_instrument.workspace/4)+flags
struc go4kFSTG_val
.amount resd 1
.size
endstruc
struc go4kFSTG_wrk
.size
endstruc
%endif
; //----------------------------------------------------------------------------------------
; // Voice struct
; //----------------------------------------------------------------------------------------
struc go4k_instrument
.release resd 1
.note resd 1
.workspace resd MAX_UNITS*MAX_UNIT_SLOTS
.dlloutl resd 1
.dlloutr resd 1
.outl resd 1
.outr resd 1
.size
endstruc
; //----------------------------------------------------------------------------------------
; // Synth struct
; //----------------------------------------------------------------------------------------
struc go4k_synth
.instruments resb go4k_instrument.size * MAX_INSTRUMENTS * MAX_VOICES
.global resb go4k_instrument.size * MAX_VOICES
.size
endstruc
%endif ; _4KLANG_INC

View File

@ -1 +0,0 @@
add_subdirectory(vsti)

12
src/introspection.asm Normal file
View File

@ -0,0 +1,12 @@
; Various compile time definitions exported
SECT_DATA(introscn)
%ifdef SU_USE_16BIT_OUTPUT
EXPORT MANGLE_DATA(su_use_16bit_output) dd 1
%else
EXPORT MANGLE_DATA(su_use_16bit_output) dd 0
%endif
%ifdef MAX_SAMPLES
EXPORT MANGLE_DATA(su_max_samples) dd MAX_SAMPLES
%endif

155
src/opcodes/arithmetic.asm Normal file
View File

@ -0,0 +1,155 @@
SECT_TEXT(suarithm)
;-------------------------------------------------------------------------------
; op_pop function: a -> (empty)
; stereo: a b -> (empty)
;-------------------------------------------------------------------------------
%if POP_ID > -1
EXPORT MANGLE_FUNC(su_op_pop,0)
%ifdef INCLUDE_STEREO_POP
jnc su_op_pop_mono
fstp st0
su_op_pop_mono:
%endif
fstp st0
ret
%endif
;-------------------------------------------------------------------------------
; op_add function: a b -> a+b b
; stereo: a b c d -> a+c b+d c d
;-------------------------------------------------------------------------------
%if ADD_ID > -1
EXPORT MANGLE_FUNC(su_op_add,0)
%ifdef INCLUDE_STEREO_ADD
jnc su_op_add_mono
fadd st0, st2
fxch
fadd st0, st3
fxch
ret
su_op_pop_mono:
%endif
fadd st1
ret
%endif
;-------------------------------------------------------------------------------
; op_addp function: a b -> a+b
; stereo: a b c d -> a+c b+d
;-------------------------------------------------------------------------------
%if ADDP_ID > -1
EXPORT MANGLE_FUNC(su_op_addp,0)
%ifdef INCLUDE_STEREO_ADDP
jnc su_op_addp_mono
faddp st2, st0
faddp st2, st0
ret
su_op_addp_mono:
%endif
faddp st1, st0
ret
%endif
;-------------------------------------------------------------------------------
; op_loadnote function: (empty) -> n
; stereo: (empty) -> n n
; ecx should point to the workspace (slightly offset)
;-------------------------------------------------------------------------------
%if LOADNOTE_ID > -1
EXPORT MANGLE_FUNC(su_op_loadnote,0)
%ifdef INCLUDE_STEREO_LOADNOTE
jnc su_op_loadnote_mono
call su_op_loadnote_mono
su_op_loadnote_mono:
%endif
fild dword [ecx+su_unit.size-su_voice.workspace+su_voice.note]
fmul dword [c_i128]
ret
%endif
;-------------------------------------------------------------------------------
; op_mul function: a b -> a*b a
; stereo: a b c d -> a*c b*d c d
;-------------------------------------------------------------------------------
%if MUL_ID > -1
EXPORT MANGLE_FUNC(su_op_mul,0)
%ifdef INCLUDE_STEREO_MUL
jnc su_op_mul_mono
fmul st0, st2
fxch
fadd st0, st3
fxch
ret
su_op_mul_mono:
%endif
fmul st1
ret
%endif
;-------------------------------------------------------------------------------
; op_mulp function: a b -> a*b
; stereo: a b c d -> a*c b*d
;-------------------------------------------------------------------------------
%if MULP_ID > -1
EXPORT MANGLE_FUNC(su_op_mulp,0)
%ifdef INCLUDE_STEREO_MULP
jnc su_op_mulp_mono
fmulp st2, st0
fmulp st2, st0
ret
su_op_mulp_mono:
%endif
fmulp st1
ret
%endif
;-------------------------------------------------------------------------------
; op_push function: a -> a a
; stereo: a b -> a b a b
;-------------------------------------------------------------------------------
%if PUSH_ID > -1
EXPORT MANGLE_FUNC(su_op_push,0)
%ifdef INCLUDE_STEREO_PUSH
jnc su_op_push_mono
fld st1
fld st1
ret
su_op_push_mono:
%endif
fld st0
ret
%endif
;-------------------------------------------------------------------------------
; op_xch function: a b -> b a
; stereo: a b c d -> c d a b
;-------------------------------------------------------------------------------
%if XCH_ID > -1
EXPORT MANGLE_FUNC(su_op_xch,0)
%ifdef INCLUDE_STEREO_XCH
jnc su_op_xch_mono
fxch st0, st2 ; c b a d
fxch st0, st1 ; b c a d
fxch st0, st2 ; d c a b
su_op_xch_mono:
%endif
fxch st0, st1
ret
%endif

168
src/opcodes/arithmetic.inc Normal file
View File

@ -0,0 +1,168 @@
;-------------------------------------------------------------------------------
; ADDP related defines
;-------------------------------------------------------------------------------
%assign ADDP_ID -1
%macro USE_ADDP 0
%if ADDP_ID == -1
%assign ADDP_ID CUR_ID
%assign CUR_ID CUR_ID + 2
%xdefine OPCODES OPCODES MANGLE_FUNC(su_op_addp,0),
%xdefine NUMPARAMS NUMPARAMS 0,
%endif
%endmacro
%macro SU_ADDP 1
USE_ADDP
%xdefine CMDS CMDS ADDP_ID + %1,
%if %1 == STEREO
%define INCLUDE_STEREO_ADDP
%endif
%endmacro
;-------------------------------------------------------------------------------
; ADD related defines
;-------------------------------------------------------------------------------
%assign ADD_ID -1
%macro USE_ADD 0
%if ADD_ID == -1
%assign ADD_ID CUR_ID
%assign CUR_ID CUR_ID + 2
%xdefine OPCODES OPCODES MANGLE_FUNC(su_op_add,0),
%xdefine NUMPARAMS NUMPARAMS 0,
%endif
%endmacro
%assign ADD_ID -1
%macro SU_ADD 1
USE_ADD
%xdefine CMDS CMDS ADD_ID + %1,
%if %1 == STEREO
%define INCLUDE_STEREO_ADD
%endif
%endmacro
;-------------------------------------------------------------------------------
; POP related defines
;-------------------------------------------------------------------------------
%assign POP_ID -1
%macro USE_POP 0
%if POP_ID == -1
%assign POP_ID CUR_ID
%assign CUR_ID CUR_ID + 2
%xdefine OPCODES OPCODES MANGLE_FUNC(su_op_pop,0),
%xdefine NUMPARAMS NUMPARAMS 0,
%endif
%endmacro
%macro SU_POP 1
USE_POP
%xdefine CMDS CMDS POP_ID + %1,
%if %1 == STEREO
%define INCLUDE_STEREO_POP
%endif
%endmacro
;-------------------------------------------------------------------------------
; LOADNOTE related defines
;-------------------------------------------------------------------------------
%assign LOADNOTE_ID -1
%macro USE_LOADNOTE 0
%if LOADNOTE_ID == -1
%assign LOADNOTE_ID CUR_ID
%assign CUR_ID CUR_ID + 2
%xdefine OPCODES OPCODES MANGLE_FUNC(su_op_loadnote,0),
%xdefine NUMPARAMS NUMPARAMS 0,
%endif
%endmacro
%macro SU_LOADNOTE 1
USE_LOADNOTE
%xdefine CMDS CMDS LOADNOTE_ID + %1,
%if %1 == STEREO
%define INCLUDE_STEREO_LOADNOTE
%endif
%endmacro
;-------------------------------------------------------------------------------
; MUL related defines
;-------------------------------------------------------------------------------
%assign MUL_ID -1
%macro USE_MUL 0
%if MUL_ID == -1
%assign MUL_ID CUR_ID
%assign CUR_ID CUR_ID + 2
%xdefine OPCODES OPCODES MANGLE_FUNC(su_op_mul,0),
%xdefine NUMPARAMS NUMPARAMS 0,
%endif
%endmacro
%macro SU_MUL 1
USE_MUL
%xdefine CMDS CMDS MUL_ID + %1,
%if %1 == STEREO
%define INCLUDE_STEREO_MUL
%endif
%endmacro
;-------------------------------------------------------------------------------
; MULP related defines
;-------------------------------------------------------------------------------
%assign MULP_ID -1
%macro USE_MULP 0
%if MULP_ID == -1
%assign MULP_ID CUR_ID
%assign CUR_ID CUR_ID + 2
%xdefine OPCODES OPCODES MANGLE_FUNC(su_op_mulp,0),
%xdefine NUMPARAMS NUMPARAMS 0,
%endif
%endmacro
%macro SU_MULP 1
USE_MULP
%xdefine CMDS CMDS MULP_ID + %1,
%if %1 == STEREO
%define INCLUDE_STEREO_MULP
%endif
%endmacro
;-------------------------------------------------------------------------------
; PUSH related defines
;-------------------------------------------------------------------------------
%assign PUSH_ID -1
%macro USE_PUSH 0
%if PUSH_ID == -1
%assign PUSH_ID CUR_ID
%assign CUR_ID CUR_ID + 2
%xdefine OPCODES OPCODES MANGLE_FUNC(su_op_push,0),
%xdefine NUMPARAMS NUMPARAMS 0,
%endif
%endmacro
%macro SU_PUSH 1
USE_PUSH
%xdefine CMDS CMDS PUSH_ID + %1,
%if %1 == STEREO
%define INCLUDE_STEREO_PUSH
%endif
%endmacro
;-------------------------------------------------------------------------------
; XCH related defines
;-------------------------------------------------------------------------------
%assign XCH_ID -1
%macro USE_XCH 0
%if XCH_ID == -1
%assign XCH_ID CUR_ID
%assign CUR_ID CUR_ID + 2
%xdefine OPCODES OPCODES MANGLE_FUNC(su_op_xch,0),
%xdefine NUMPARAMS NUMPARAMS 0,
%endif
%endmacro
%macro SU_XCH 1
USE_XCH
%xdefine CMDS CMDS XCH_ID + %1,
%if %1 == STEREO
%define INCLUDE_STEREO_XCH
%endif
%endmacro

351
src/opcodes/effects.asm Normal file
View File

@ -0,0 +1,351 @@
;-------------------------------------------------------------------------------
; DISTORT Tick
;-------------------------------------------------------------------------------
; Input: st0 : x - input value
; Output: st0 : x*a/(1-a+(2*a-1)*abs(x))
; where x is clamped first
;-------------------------------------------------------------------------------
%if DISTORT_ID > -1
SECT_TEXT(sudistrt)
EXPORT MANGLE_FUNC(su_op_distort,0)
%ifdef INCLUDE_STEREO_DISTORT
jnc su_op_distort_mono
call su_stereo_filterhelper
%define INCLUDE_STEREO_FILTERHELPER
su_op_distort_mono:
%endif
fld dword [edx+su_distort_ports.drive]
%define SU_INCLUDE_WAVESHAPER
; flow into waveshaper
%endif
%ifdef SU_INCLUDE_WAVESHAPER
su_waveshaper:
fxch ; x a
call MANGLE_FUNC(su_clip_op,0)
fxch ; a x' (from now on just called x)
fld st0 ; a a x
fsub dword [c_0_5] ; a-.5 a x
fadd st0 ; 2*a-1 a x
fld st2 ; x 2*a-1 a x
fabs ; abs(x) 2*a-1 a x
fmulp st1 ; (2*a-1)*abs(x) a x
fld1 ; 1 (2*a-1)*abs(x) a x
faddp st1 ; 1+(2*a-1)*abs(x) a x
fsub st1 ; 1-a+(2*a-1)*abs(x) a x
fdivp st1, st0 ; a/(1-a+(2*a-1)*abs(x)) x
fmulp st1 ; x*a/(1-a+(2*a-1)*abs(x))
ret
%define SU_INCLUDE_CLIP
%endif ; SU_USE_DST
;-------------------------------------------------------------------------------
; HOLD Tick
;-------------------------------------------------------------------------------
%if HOLD_ID > -1
SECT_TEXT(suhold)
EXPORT MANGLE_FUNC(su_op_hold,0)
%ifdef INCLUDE_STEREO_HOLD
jnc su_op_hold_mono
call su_stereo_filterhelper
%define INCLUDE_STEREO_FILTERHELPER
su_op_hold_mono:
%endif
fld dword [edx+su_hold_ports.freq] ; f x
fmul st0, st0 ; f^2 x
fchs ; -f^2 x
fadd dword [WRK+su_hold_wrk.phase] ; p-f^2 x
fst dword [WRK+su_hold_wrk.phase] ; p <- p-f^2
fldz ; 0 p x
fucomip st1 ; p x
fstp dword [esp-4] ; t=p, x
jc short su_op_hold_holding ; if (0 < p) goto holding
fld1 ; 1 x
fadd dword [esp-4] ; 1+t x
fstp dword [WRK+su_hold_wrk.phase] ; x
fst dword [WRK+su_hold_wrk.holdval] ; save holded value
ret ; x
su_op_hold_holding:
fstp st0 ;
fld dword [WRK+su_hold_wrk.holdval] ; x
ret
%endif ; HOLD_ID > -1
;-------------------------------------------------------------------------------
; su_op_filter: perform low/high/band-pass filtering on the signal
;-------------------------------------------------------------------------------
; Input: WRK : pointer to unit workspace
; VAL : pointer to unit values as bytes
; ecx : pointer to global workspace
; st0 : signal
; Output: st0 : filtered signal
; Dirty: eax, edx
;-------------------------------------------------------------------------------
%if FILTER_ID > -1
SECT_TEXT(sufilter)
EXPORT MANGLE_FUNC(su_op_filter,0)
lodsb ; load the flags to al
%ifdef INCLUDE_STEREO_FILTER
jnc su_op_filter_mono
call su_stereo_filterhelper
%define INCLUDE_STEREO_FILTERHELPER
su_op_filter_mono:
%endif
fld dword [edx+su_filter_ports.res] ; r x
fld dword [edx+su_filter_ports.freq]; f r x
fmul st0, st0 ; f2 x (square the input so we never get negative and also have a smoother behaviour in the lower frequencies)
fst dword [esp-4] ; f2 r x
fmul dword [WRK+su_filter_wrk.band] ; f2*b r x
fadd dword [WRK+su_filter_wrk.low] ; f2*b+l r x
fst dword [WRK+su_filter_wrk.low] ; l'=f2*b+l r x
fsubp st2, st0 ; r x-l'
fmul dword [WRK+su_filter_wrk.band] ; r*b x-l'
fsubp st1, st0 ; x-l'-r*b
fst dword [WRK+su_filter_wrk.high] ; h'=x-l'-r*b
fmul dword [esp-4] ; f2*h'
fadd dword [WRK+su_filter_wrk.band] ; f2*h'+b
fstp dword [WRK+su_filter_wrk.band] ; b'=f2*h'+b
fldz ; 0
%ifdef INCLUDE_LOWPASS
test al, byte LOWPASS
jz short su_op_filter_skiplowpass
fadd dword [WRK+su_filter_wrk.low]
su_op_filter_skiplowpass:
%endif
%ifdef INCLUDE_BANDPASS
test al, byte BANDPASS
jz short su_op_filter_skipbandpass
fadd dword [WRK+su_filter_wrk.band]
su_op_filter_skipbandpass:
%endif
%ifdef INCLUDE_HIGHPASS
test al, byte HIGHPASS
jz short su_op_filter_skiphighpass
fadd dword [WRK+su_filter_wrk.high]
su_op_filter_skiphighpass:
%endif
%ifdef INCLUDE_NEGBANDPASS
test al, byte NEGBANDPASS
jz short su_op_filter_skipnegbandpass
fsub dword [WRK+su_filter_wrk.band]
su_op_filter_skipnegbandpass:
%endif
%ifdef INCLUDE_NEGHIGHPASS
test al, byte NEGHIGHPASS
jz short su_op_filter_skipneghighpass
fsub dword [WRK+su_filter_wrk.high]
su_op_filter_skipneghighpass:
%endif
ret
%endif ; SU_INCLUDE_FILTER
;-------------------------------------------------------------------------------
; su_clip function
;-------------------------------------------------------------------------------
; Input: st0 : x
; Output: st0 : min(max(x,-1),1)
;-------------------------------------------------------------------------------
%if CLIP_ID > -1
%define SU_INCLUDE_CLIP
%endif
%ifdef SU_INCLUDE_CLIP
SECT_TEXT(suclip)
EXPORT MANGLE_FUNC(su_clip_op,0)
fld1 ; 1 x a
fucomi st1 ; if (1 <= x)
jbe short su_clip_do ; goto Clip_Do
fchs ; -1 x a
fucomi st1 ; if (-1 < x)
fcmovb st0, st1 ; x x a
su_clip_do:
fstp st1 ; x' a, where x' = clamp(x)
ret
%endif ; SU_INCLUDE_CLIP
;-------------------------------------------------------------------------------
; PAN Tick
;-------------------------------------------------------------------------------
; Input: WRK : pointer to unit workspace
; VAL : pointer to unit values as bytes
; ecx : pointer to global workspace
; st0 : s, the signal
; Output: st0 : s*(1-p), where p is the panning in [0,1] range
; st1 : s*p
; Dirty: eax, edx
;-------------------------------------------------------------------------------
%if PAN_ID > -1
SECT_TEXT(supan)
%ifdef INCLUDE_STEREO_PAN
EXPORT MANGLE_FUNC(su_op_pan,0)
jc su_op_pan_do ; this time, if this is mono op...
fld st0 ; ...we duplicate the mono into stereo first
su_op_pan_do:
fld dword [edx+su_pan_ports.panning] ; p l r
fld1 ; 1 p l r
fsub st1 ; 1-p p l r
fmulp st2 ; p (1-p)*l r
fmulp st2 ; (1-p)*l p*r
ret
%else ; ifndef INCLUDE_STEREO_PAN
EXPORT MANGLE_FUNC(su_op_pan,0)
fld dword [edx+su_pan_ports.panning] ; p s
fmul st1 ; p*s s
fsub st1, st0 ; p*s s-p*s
; Equal to
; s*p s*(1-p)
fxch ; s*(1-p) s*p SHOULD PROBABLY DELETE, WHY BOTHER
ret
%endif ; INCLUDE_STEREO_PAN
%endif ; SU_USE_PAN
;-------------------------------------------------------------------------------
; su_stereo_filterhelper: moves the workspace to next, does the filtering for
; right channel (pulling the calling address from stack), rewinds the
; workspace and returns
;-------------------------------------------------------------------------------
%ifdef INCLUDE_STEREO_FILTERHELPER
su_stereo_filterhelper:
add WRK, 16
fxch ; r l
call dword [esp] ; call whoever called me...
fxch ; l r
sub WRK, 16 ; move WRK back to where it was
ret
%endif
;-------------------------------------------------------------------------------
; Delay Tick
;-------------------------------------------------------------------------------
; Pseudocode:
; q = dr*x
; for (i = 0;i < count;i++)
; s = b[(t-delaytime[i+offset])&65535]
; q += s
; o[i] = o[i]*da+s*(1-da)
; b[t] = f*o[i] +p^2*x
; Perform dc-filtering q and output
;-------------------------------------------------------------------------------
%if DELAY_ID > -1
SECT_TEXT(sudelay)
EXPORT MANGLE_FUNC(su_op_delay,0)
lodsb ; eax = delay index
mov edi, eax
lodsb ; eax = delay count
%ifdef INCLUDE_STEREO_DELAY
jnc su_op_delay_mono
fxch
call su_op_delay_mono ; do right delay
fxch
add edi, eax ; the second delay is done with the delay time index added by count
su_op_delay_mono:
%endif
pushad
mov ebx, edi; ugly register juggling, refactor
%ifdef DELAY_NOTE_SYNC
test ebx, ebx ; note s
jne su_op_delay_skipnotesync
fld1
fild dword [ecx+su_unit.size-su_voice.workspace+su_voice.note]
fmul dword [c_i12]
call MANGLE_FUNC(su_power,0)
fmul dword [c_freq_normalize] ; // normalize
fdivp st1, st0 ; // invert to get numer of samples
fistp word [MANGLE_DATA(su_delay_times)] ; store current comb size
su_op_delay_skipnotesync:
%endif
kmDLL_func_process:
mov ecx, eax ;// ecx is the number of parallel delays
mov WRK, dword [MANGLE_DATA(su_delay_buffer_ofs)] ;// ebp is current delay
fld st0 ; x x
fmul dword [edx+su_delay_ports.dry] ; dr*x x
fxch ; x dr*x
fmul dword [edx+su_delay_ports.pregain] ; p*x dr*x
fmul dword [edx+su_delay_ports.pregain] ; p^2*x dr*x
kmDLL_func_loop:
mov edi, dword [WRK + su_delayline_wrk.time]
inc edi
and edi, MAX_DELAY-1
mov dword [WRK + su_delayline_wrk.time],edi
movzx esi, word [MANGLE_DATA(su_delay_times)+ebx*2] ; esi = comb size from the delay times table
mov eax, edi
sub eax, esi
and eax, MAX_DELAY-1
fld dword [WRK+eax*4+su_delayline_wrk.buffer] ; s p^2*x dr*x, where s is the sample from delay buffer
;// add comb output to current output
fadd st2, st0 ; s p^2*x dr*x+s
fld1 ; 1 s p^2*x dr*x+s
fsub dword [edx+su_delay_ports.damp] ; 1-da s p^2*x dr*x+s
fmulp st1, st0 ; s*(1-da) p^2*x dr*x+s
fld dword [edx+su_delay_ports.damp] ; da s*(1-da) p^2*x dr*x+s
fmul dword [WRK+su_delayline_wrk.filtstate] ; o*da s*(1-da) p^2*x dr*x+s, where o is stored
faddp st1, st0 ; o*da+s*(1-da) p^2*x dr*x+s
fst dword [WRK+su_delayline_wrk.filtstate] ; o'=o*da+s*(1-da), o' p^2*x dr*x+s
fmul dword [edx+su_delay_ports.feedback] ; f*o' p^2*x dr*x+s
fadd st0, st1 ; f*o'+p^2*x p^2*x dr*x+s
fstp dword [WRK+edi*4+su_delayline_wrk.buffer]; save f*o'+p^2*x to delay buffer
inc ebx ;// go to next delay lenkmh index
add WRK, su_delayline_wrk.size ;// go to next delay
mov dword [MANGLE_DATA(su_delay_buffer_ofs)], WRK ;// store next delay offset
loopne kmDLL_func_loop
fstp st0 ; dr*x+s1+s2+s3+...
; DC-filtering
sub WRK, su_delayline_wrk.size ; the reason to use the last su_delayline_wrk instead of su_delay_wrk is that su_delay_wrk is wiped by retriggering
fld dword [WRK+su_delayline_wrk.dcout] ; o s
fmul dword [c_dc_const] ; c*o s
fsub dword [WRK+su_delayline_wrk.dcin] ; c*o-i s
fxch ; s c*o-i
fst dword [WRK+su_delayline_wrk.dcin] ; i'=s, s c*o-i
faddp st1 ; s+c*o-i
fadd dword [c_0_5] ;// add and sub small offset to prevent denormalization
fsub dword [c_0_5]
fst dword [WRK+su_delayline_wrk.dcout] ; o'=s+c*o-i
popad
ret
;-------------------------------------------------------------------------------
; Delay data
;-------------------------------------------------------------------------------
SECT_BSS(sudelbuf)
EXPORT MANGLE_DATA(su_delay_buffer_ofs)
resd 1
EXPORT MANGLE_DATA(su_delay_buffer)
resb NUM_DELAY_LINES*su_delayline_wrk.size
SECT_DATA(suconst)
%ifndef C_DC_CONST
c_dc_const dd 0.99609375 ; R = 1 - (pi*2 * frequency /samplerate)
%define C_DC_CONST
%endif
%ifndef C_FREQ_NORMALIZE
c_freq_normalize dd 0.000092696138 ; // 220.0/(2^(69/12)) / 44100.0
%define C_FREQ_NORMALIZE
%endif
%endif ; DELAY_ID > -1

Some files were not shown because too many files have changed in this diff Show More