Commit 718ba6f4 authored by PoroCYon's avatar PoroCYon
Browse files

add clinkster source (doesn't seem to have a public git repo)

parent 43cccc56
clinkster/Clinkster/* linguist-vendored
CLINKSTER - a software synthesizer for 4k intros by Blueberry / Loonies
INTRODUCTION
Clinkster is a software synthesizer (or synth, for short) designed for use in
extremely size-restricted executables, such a 4k intros. It has been under
development since 2008 and used in several 4k intros through the years. For
some background reading on the ideas behind it and its early history, refer
to the article from Zine #14 "Development Diary of Luminagia":
http://zine.bitfellas.org/article.php?zine=14&id=24
FEATURES
- VST instrument for Windows and Mac OS X, with source
- Simple interface: create your instruments using just 18 sliders
- Multi-layered, delicious, voluminous stereo sound based on phase modulation
- Unlimited number of tracks and unlimited polyphony per track
- Player source for Windows - integrate with C++ or asm
- Excludes unused features from player code to save space
- Easy-exe setup for creating executable music - no coding or additional
installation needed
- Many example songs and instruments included
OVERVIEW
The Clinkster toolchain consists of three parts:
1. A VST instrument to use when composing the music. This can in principle be
used with any VSTi host, but the rest of the toolchain is designed to be
used with Renoise, so if you want to use your music in an executable, you
will need to make your music in Renoise.
2. A conversion script - RenoiseConvert.py - to convert a Renoise song using
Clinkster instruments into a .asm file containing the music data in the
format needed by the executable player. To run the conversion script, you
will need to install Python 2.x (where x >= 5).
3. Player code to include in your intro. Two versions are provided:
clinkster.asm and clinkster_multithreaded.asm. The multithreaded version
is bigger but computes the music twice as fast (provided you have at least
two CPU cores). Both of these assume the converted music in music.asm.
The clinkster.h and clinkster.inc files contain definitions for using the
synth from C/C++ or asm, respectively. Refer to these files for detailed
usage information.
PARAMETERS
Clinkster is designed to be used with the built-in VST parameter adjustment
GUI in Renoise.
The numbers in parentheses after the value of each parameter indicates the
byte value that will be used to represent that parameter value in the
executable version of the music. Using the same byte values across parameters
and across instruments generally leads to a more compact representation of the
music.
The sliders in Renoise have 101 different positions, and for most of the
parameters, these correspond to successive byte values. The exceptions are:
- B PitchD, M PitchD, IndexD, Attack, Decay, Release, Gain: One position for
every two values.
- B Pitch and M Pitch: 5 positions for every 12 values (one octave).
- RandomSeed: 128 values in total.
If you want a value in between two slider-accessible values, click on the
value to access the internal representation (0% for minimum, 100% for maximum)
and modify it slightly.
As for the meaning of the individual parameters, experimentation is the key.
Be sure to test your instrument in many different octaves. To get you started,
here is a brief description of the parameters:
BaseWave, ModWave: Waveforms for the two oscillators. The ModWave modulates
the phase of the BaseWave.
B Detune, M Detune: Randomly varies the frequencies of the two oscillators
across layers and across the left and right channel.
B Pitch, M Pitch: Pitch the oscillator up or down relative to the played note.
B PitchD, M PitchD (pitch decay): Decay the pitch towards (or away from) the
played note.
Index: Strength of the phase modulation.
IndexSpr (index spread): Randomly varies the modulation strength across layers
and across the left and right channel.
IndexD (index decay): Decay the modulation strength towards (or away from)
zero.
Layers: Number of layers of sound to compute, with individual, random
variations controlled by the detune and index spread parameters. More
layers give a fatter, more chorus-like sound but also increases the
computational load of the instrument considerably.
RandomSeed: Seed used for the random variations across layers and channels.
Adjust this until you get a balanced, in-key sound.
Attack, Decay, Sustain, Release: Control the amplitude envelope of the sound.
Setting Sustain to a negative value creates a "double attack" where the
amplitude crosses zero during the decay.
Gain: Amplify the sound after the amplitude envelope and apply soft clipping.
Results in distorted or compressed sounds.
Also take a look at the included example instruments and example songs for
inspiration.
USING THE VST
In order to use the music in an executable, there are a couple of guidelines
you need to follow when creating your music in Renoise.
You can use per-note velocity, but no other in-track effect commands.
You can use any number of tracks and any number of note columns per track.
You can adjust the volume and panning of tracks using the Volume/Panning Track
DSP, the post-DSP volume/panning, the mixer and the master volume.
You can use the Delay effect (under Track DSPs) with these restrictions:
- The "L Feedb.", "R Feedb." and "Send" sliders must be at the same position.
- "Mute Src." must be off.
- No L/R Output Pan.
- If you use the Delay effect on multiple tracks, you must use the exact same
parameters on each track.
To make it easier to control the volume and panning of a group of tracks, or
to use the same delay on multiple tracks, you can use a #Send DSP set to "Mute
Source" to route the sound from the track to a Send track. You can then set
the desired volume, panning and delay on the Send track. Only the final track
of a Send chain can use delay.
The VST is designed to run in one instance per instrument and supports
unlimited polyphony and unlimited reuse of instruments across tracks. Note
however, that if you play notes using the same instrument in multiple tracks
at the same time, Renoise will play the notes using a single VST instance and
output the result in the track in which the last note was triggered. This
works fine as long as the tracks use the same volume, panning and delay. If
this is not the case, you will need to use separate copies of the instrument
for each different track. Otherwise, you will get clicks and generally a
different result from what you expected.
In the pattern sequence matrix (available to the left of the pattern view),
you can mute individual tracks at specific positions. This muting is taken
into account by the converter.
The VST works with any sample rate. To match the sound produced by the player,
use a sample rate of 44100Hz. If your music is too computationally heavy for
your CPU to cope with, you can lighten the load by lowering the sample rate
while composing, though that will of course have a detrimental effect on the
sound quality.
USING THE CONVERTER
The RenoiseConvert.py script in the converter directory (or, equivalently,
RenoiseConvert.exe in easy_exe/tools) will convert a Renoise song using
Clinkster into a .asm file to use with the supplied player source.
During conversion, each note column in a track will become a separate track in
the converted music. If you use more than one instrument inside a single note
column, that column will be split by the converter into one track per
instrument. Also, if you have used the delay effect, all tracks using delay
will be put before the tracks not using delay.
The converter will print a list of the resulting tracks, along with the
original track / instrument combinations they correspond to.
For each track, it will print a list of tone / length / velocity combinations
used in that track, in the form NOTE/VELOCITY:LENGTH(NUMBER) or
NOTE:LENGTH(NUMBER). For instance, C-4/6A:4(32) means that C-4 notes with
velocity 6A (hex) and length 4 occur 32 times in the track. If the velocity is
omitted, it is 127 (7F hex - maximum). The number of different combinations
used in the track has influence on the size of the resulting music (as well as
its precalculation time), so this list can be useful as a guide for optimizing
your music to take up less space.
At the end, the converter prints a list of the optional features used in the
music. Each of these features has some cost in terms of the code size of the
player. The options are:
SINE, SAWTOOTH, SQUARE, PARABOLA, TRIANGLE, NOISE:
The corresponding waveform is used in some instrument.
VELOCITY: One or more notes have velocity less than 127.
LONG_NOTES: One or more notes are longer than 127 rows.
DELAY: The delay device is used.
PANNING: One or more tracks have non-center panning.
INDEXDECAY: Some instrument has IndexDecay different from 0.
GAIN: Some instrument has Gain different from 1.
FEEDBACK
I am always very interested in hearing about your adventures with Clinkster,
and to help out if you encounter problems.
Send stories, comments, bug reports and questions to blueberry@loonies.dk or
post them to the Pouet forum at http://pouet.net/prod.php?which=61592
ACKNOWLEDGEMENTS
Thanks to all the people who have tried out this synth (as musician and/or
coder) and given good feedback for its development: Bod, Bstrr, Curt Cool,
Eladamri, El Blanco, Farfar, Garfferen, Hardy, Lemmus, Loaderror, Maytz,
Neoman, Psycho, Punqtured, Response, Seven, TheT, Xerxes, and the ones I
have forgotten.
This diff is collapsed.
#!/usr/bin/env python
import xml.dom
import xml.dom.minidom
class XML(object):
def __init__(self, domlist):
self.domlist = list(domlist)
def __getattr__(self, name):
l = []
for d in self.domlist:
for c in d.childNodes:
if c.nodeName == name:
l.append(c)
return XML(l)
def __len__(self):
return len(self.domlist)
def __getitem__(self, i):
if i >= len(self.domlist):
return XML([])
return XML([self.domlist[i]])
def __iter__(self):
for d in self.domlist:
yield XML([d])
def __call__(self, attrname):
s = ""
for d in self.domlist:
if d.nodeType == xml.dom.Node.ELEMENT_NODE and d.hasAttribute(attrname):
s += d.getAttribute(attrname)
return s
def __str__(self):
def collect(dl):
s = ""
for d in dl:
if d.nodeType == xml.dom.Node.TEXT_NODE:
s += d.data
else:
s += collect(d.childNodes)
return s
return collect(self.domlist)
def __int__(self):
return int(str(self))
def __float__(self):
return float(str(self))
def __nonzero__(self):
return len(self.domlist) != 0
def replaceText(self, fun):
def collect(dl):
for d in dl:
if d.nodeType == xml.dom.Node.TEXT_NODE:
d.data = fun(d.data)
else:
collect(d.childNodes)
collect(self.domlist)
def setData(self, data):
sdata = str(data)
for d in self.domlist:
for c in d.childNodes:
c.data = sdata
def removeChild(self, child):
if len(self.domlist) != len(child.domlist):
raise ValueError
for p,c in zip(self.domlist, child.domlist):
p.removeChild(c)
def insertBefore(self, newChild, refChild):
if len(self.domlist) != len(newChild.domlist) or len(newChild.domlist) != len(refChild.domlist):
raise ValueError
for p,nc,rc in zip(self.domlist, newChild.domlist, refChild.domlist):
p.insertBefore(nc.childNodes[0],rc)
def export(self):
return "".join(x.toxml("utf-8") for x in self.domlist)
def readXML(filename):
return XML([xml.dom.minidom.parse(filename)])
def makeXML(xstring):
return XML([xml.dom.minidom.parseString(xstring)])
del /q temp\*
del music.exe
tools\RenoiseConvert.exe music.xrns temp\music.asm
tools\nasmw -f win32 src\clinkster_multithreaded.asm -o temp\clinkster_multithreaded.obj
tools\nasmw -f win32 src\play.asm -o temp\play.obj
tools\crinkler20\crinkler temp\clinkster_multithreaded.obj temp\play.obj /OUT:music.exe /ENTRY:main tools\kernel32.lib tools\user32.lib tools\winmm.lib tools\msvcrt_old.lib @crinkler_options.txt
pause
del /q temp\*
del music_wav.exe
tools\RenoiseConvert.exe music.xrns temp\music.asm
tools\nasmw -f win32 src\clinkster_multithreaded.asm -o temp\clinkster_multithreaded.obj
tools\nasmw -f win32 -dWRITE_WAV src\play.asm -o temp\play.obj
tools\crinkler20\crinkler temp\clinkster_multithreaded.obj temp\play.obj /OUT:music_wav.exe /ENTRY:main tools\kernel32.lib tools\user32.lib tools\winmm.lib tools\msvcrt_old.lib @crinkler_options.txt
pause
/UNSAFEIMPORT /COMPMODE:INSTANT /HASHSIZE:100
Music composed using 4k synth "Clinkster" by Blueberry / Loonies
Generating music...
This setup is for easily building an executable version of a piece of music
created using Clinkster.
Proceed as follows:
1. Place your music here, named music.xrns.
2. Place a text file containing the text you would like the executable to
print at startup, named music.txt.
3. Optionally modify the Crinkler options in crinkler_options.txt
(read the Crinkler manual for details).
4. Run build.bat to get an executable that plays the music, or
build_wav.bat to get one that writes the music in WAV format to
music.wav and then plays it.
Enjoy!
extern Clinkster_GenerateMusic
extern Clinkster_StartMusic
extern Clinkster_GetPosition
extern Clinkster_GetInstrumentTrigger
extern Clinkster_MusicBuffer
extern Clinkster_NoteTiming
extern Clinkster_TicksPerSecond
extern Clinkster_MusicLength
extern Clinkster_NumTracks
extern Clinkster_WavFileHeader
; If set to 1, timing information is generated during music generation
; which is needed for Clinkster_GetInstrumentTrigger.
; Set it to 0 if you don't need this functionality.
%define CLINKSTER_GENERATE_TIMING_DATA 0
; Offset applied by Clinkster_GetPosition to compensate for graphics latency.
; Measured in samples (44100ths of a second).
; The default value of 2048 (corresponding to about 46 milliseconds) is
; appropriate for typical display latencies for high-framerate effects.
%define CLINKSTER_TIMER_OFFSET 0
%include "temp/music.asm"
;; ********** Definitions **********
global Clinkster_GenerateMusic
global _Clinkster_GenerateMusic@0
global Clinkster_StartMusic
global _Clinkster_StartMusic@0
global Clinkster_GetPosition
global _Clinkster_GetPosition@0
global Clinkster_GetInstrumentTrigger
global _Clinkster_GetInstrumentTrigger@8
global Clinkster_MusicBuffer
global _Clinkster_MusicBuffer
global Clinkster_TicksPerSecond
global _Clinkster_TicksPerSecond
global Clinkster_MusicLength
global _Clinkster_MusicLength
global Clinkster_NumTracks
global _Clinkster_NumTracks
%if CLINKSTER_GENERATE_TIMING_DATA
global Clinkster_NoteTiming
global _Clinkster_NoteTiming
%endif
global Clinkster_WavFileHeader
global _Clinkster_WavFileHeader
extern __imp__waveOutOpen@24
extern __imp__waveOutPrepareHeader@12
extern __imp__waveOutWrite@12
extern __imp__waveOutGetPosition@12
extern __imp__CreateThread@24
extern __imp__WaitForSingleObject@8
%define SAMPLE_RATE 44100
%define WAVE_SIZE 65536
;; ********** Public variables **********
section MusBuf bss align=4
Clinkster_MusicBuffer:
_Clinkster_MusicBuffer:
.align24
resw (TOTAL_SAMPLES*2)
resw 2 ; padding to catch extra write in conversion
section tps rdata align=4
Clinkster_TicksPerSecond:
_Clinkster_TicksPerSecond:
dd TICKS_PER_SECOND
section muslen rdata align=4
Clinkster_MusicLength:
_Clinkster_MusicLength:
dd MUSIC_LENGTH
section numtr rdata align=4
Clinkster_NumTracks:
_Clinkster_NumTracks:
dd NUMTRACKS
%if CLINKSTER_GENERATE_TIMING_DATA
section musdat bss align=4
Clinkster_NoteTiming:
_Clinkster_NoteTiming:
.align16
resd 2*(NUMTRACKS<<LOGNUMTICKS)
section timing data align=4
timing_ptr: dd Clinkster_NoteTiming
%endif
section WavFile rdata align=4
Clinkster_WavFileHeader:
_Clinkster_WavFileHeader:
db "RIFF"
dd 36+TOTAL_SAMPLES*4
db "WAVE"
db "fmt "
dd 16
dw 1,2
dd SAMPLE_RATE
dd SAMPLE_RATE*4
dw 4,16
db "data"
dd TOTAL_SAMPLES*4
;; ********** System structures **********
section WaveForm rdata align=1
_WaveFormat:
dw 1,2
dd SAMPLE_RATE
dd SAMPLE_RATE*4
dw 4,16,0
section WaveHdr data align=4
_WaveHdr:
dd Clinkster_MusicBuffer
dd (TOTAL_SAMPLES*4)
dd 0,0,0,0,0,0
section wavehand bss align=4
_WaveOutHandle:
.align16
resd 1
section WaveTime data align=4
_WaveTime:
dd 4,0,0,0,0,0,0,0
;; ********** Internal buffers **********
section wforms bss align=4
waveforms:
.align16
resd 6*WAVE_SIZE
;; ********** Instrument parameter access **********
section paramw rdata align=4
param_weights:
dd 0.125 ; Release
dd 0.125 ; Decay
dd 0.125 ; Attack
%if USES_GAIN
dd 0.125 ; Gain
%endif
%if USES_INDEXDECAY
dd 0.0009765625 ; IndexDecay
%endif
dd 0.0009765625 ; M PitchDecay
dd 0.0009765625 ; B PitchDecay
dd 0.083333333333 ; M Pitch
dd 0.083333333333 ; B Pitch
dd 0.0000152587890625 ; Volume
%if USES_PANNING
dd 0.0000152587890625 ; Volume
%endif
dd 0.03125 ; Sustain
dd 16307 ; RandomSeed
dd 1 ; Layers
dd 4096.0 ; Index
dd 0.125 ; Index Spread
dd 0.0009765625 ; M Detune
dd 0.0009765625 ; B Detune
dd 65536 ; ModWave
dd 65536 ; BaseWave
struc instr_params
ip_basewave: resd 1
ip_modwave: resd 1
ip_bdetune: resd 1
ip_mdetune: resd 1
ip_indexspr: resd 1
ip_index: resd 1
ip_layers: resd 1
ip_randomseed: resd 1
ip_sustain: resd 1
ip_volume: resd 1+USES_PANNING
ip_bpitch: resd 1
ip_mpitch: resd 1
ip_bpitchd: resd 1
ip_mpitchd: resd 1
%if USES_INDEXDECAY
ip_indexd: resd 1
%endif
%if USES_GAIN
ip_gain: resd 1
%endif
ip_attack: resd 1
ip_decay: resd 1
ip_release: resd 1
endstruc
%define ip_INT 0
%define ip_FLOAT 0
%define IP(f,t) dword [dword ebx + g_instrparams + ip_ %+ f + ip_ %+ t]
%define IPI(f,i,t) dword [dword ebx + g_instrparams + ip_ %+ f + ip_ %+ t + i]
;; ********** Internal constants and tables **********
section resamp rdata align=4
resamplefilter:
db -1,-2,-4,-4,-2,3,14,30,51,98,116,126
db 126,116,98,51,30,14,3,-2,-4,-4,-2,-1
resamplefilter_end:
FILTER_SIZE equ (resamplefilter_end-resamplefilter)
section wavestep rdata align=4
c_wavestep: dd 0.000030517578125
section basefreq rdata align=4
c_basefreq: dd 2.86698696365342
section halfnote rdata align=4
c_halfnote: dd 1.05946309435929
section finalamp rdata align=4
c_finalamp: dd 32767
section velfac rdata align=4
c_velocityfac: dd 0.007874015748031496
section delaystr rdata align=4
c_delaystr: dd DELAY_STRENGTH
section offset rdata align=4
c_timeoffset: dd CLINKSTER_TIMER_OFFSET*4
section tempo rdata align=4
c_ticklength: dd SUBSAMPLES_PER_TICK/4*4
section half rdata align=4
c_onehalf: dd 0.5
;; ********** Internal global variables **********
struc globalvars
g_phasetemp: resd 1
g_layer_random: resd 1
g_stereo: resd 1 ; 0 for left channel, 2 for right channel
g_noteposptr: resd 1
g_notesamptr: resd 1
g_instrparams: resb instr_params_size
g_layerparams: resq 0
g_layer_bfreq: resq 1
g_layer_mfreq: resq 1
g_layer_index: resq 1
g_layer_bpitch: resq 1
g_layer_mpitch: resq 1