Hi,
I’ve created a little matlab script that generates samples with given
waveforms, frequencies and amplitudes. It can easily be extended
with some batch processing to spit out hundreds of basic waveforms
with different frequencies, lengths, waveshapes, and more.
Are anyone interested in this? If so, any ideas for extensions are
welcome. Also, how many samples are needed? One for each note
is just as simple to do as one for each octave, but will increase
the size of the sample package a lot…
Current parameters:
samplerate
length in seconds
These can be varied in time:
frequency
amplitude
And I’ve also added some definable functions for:
waveshaping (mapping of one sample value to another)
phase distortion (mapping of one phase value to another)
More can be added, if there’s interest and good ideas.
I’ve found that matlab is very easy to program for this kind of task.
To keep the package small, but versatile, I suggest picking a set of
different combinations of waveforms and waveshaping functions,
and keeping the frequency and amplitudes constant. This will give a
package of basic waveforms that can be looped and modified at will.
Then more can follow in other packages, whenever I feel like experimenting.
Personally, I’ve done such things with CSound some time ago.
I’ve also done a chiptune XM with those CSound microsamples.
You can download it here (5kb) (played correctly by FT2 and XmPlay, I don’t know about the other players)
Had to be the soundtrack of a really acid 64kb intro which has never been released (lazy coders!! )
About the number of samples to generate, I think that one per octave is far enough: after all, at the moment you can’t put more than 16 samples in an instrument, so you should use more than one instrument per… instrument, and this would be a considerable waste of lifetime to manage for a chiptune in my opinion
I do this mostly to experiment with generating sounds from the
bottom up. And then I mean from the pure mathematics, so I know
exactly what happens and can reproduce it later in other contexts.
We’ll see how far I push this little script, I don’t have any huge plans.
I’ve currently started reading a bit on physical modelling, hope to get
to some experimenting done there as well before the year is over.
(Maybe that’ll even lead to something useful…)
At lower frequencies (notes) you could stick with 1 sample per octave. But when you move up to a higher octave(ex. C-4) you need to have at least 2 samples/octave. At C-5/C-6 maybe 4-5 samples/octave. Else you will get unwanted noises (alias-free waveforms). It’s hard to get good chip samples
at C-6 or higher.
A bit off topic but a multi layered instrument editor would be nice. Extend the number of samples to 64 or even 128. Build your own synth with chip samples, Lfo’s, Envelopes etc. and use the built in effects…
I’m not a serious coder and I guess it would be a lot of work … so I probably should keep my mouth shut …
Edit: Was a bit tired yesterday … Didn’t mean oscillator’s … changed to Envelopes
There are many VSTis capable of making chip sounds. But considering that Renoise has LFOs, filter envelopes and stuff, a small generator feeding the sampler of Renoise would be a great thing. Something like OSCx3 in FL.
Ok, I’m posting the script here. Comments in the file should explain how it
works. Some basic math, matlab and sound knowledge doesn’t hurt.
I don’t have time to do much with this now the next month or two, have
to focus on Renoise development and studies. I planned to add some outer
loops with batch processing, to generate many waveforms in one run.
This is a simple extension, for example using cell arrays with “funcs” structures.
Two files follow.
% -------------------------------------------------------------------------
% waveGenerator.m
%
% Author: Martin Alnæs
%
% Licence: Do whatever you want with this. I don't care. I take no
% responsibility whatsoever for anything that might happen.
% Take care of your ears :)
%
% -------------------------------------------------------------------------
% -------------------------------------------------------------------------
% What to do with the generated data, for convenience only since of course,
% this can be handled manually afterwards by using the "wave" vector.
% -------------------------------------------------------------------------
doPlot = true;
doPlay = true;
doSave = false;
filename = 'test.wav';
% -------------------------------------------------------------------------
% Time parameters
% -------------------------------------------------------------------------
% samplerate
samplerate = 44100;
% seconds of wave to be generated
seconds = 1;
% -------------------------------------------------------------------------
% Various functions defining the wave generation.
% All functions must be vectorized. See generateWave for descriptions.
% -------------------------------------------------------------------------
% ------ frequency function
frequencyFunc = inline('440'); % constant
%frequencyFunc = inline('4');
%frequencyFunc = inline('119.5');
%frequencyFunc = inline('exp(t)'); % exponentially growing
% ------ amplitude function
amplitudeFunc = inline('1'); % constant
%amplitudeFunc = inline(['(exp(t)-1) / (exp(max(t))-1)']); % exponentially growing
%amplitudeFunc = inline(['(exp(max(t)-t)-1) / (exp(max(t))-1)']); % exponentially sinking
% ------ shape function
shapeFunc = inline('sin(2*pi*x)'); % sine
%shapeFunc = inline('mod(x,1) < 0.5'); % square
%shapeFunc = inline('mod(x+0.5,1)'); % rising saw
%shapeFunc = inline('-mod(x+0.5,1)'); % falling saw
% ------ phase distortion function
phaseDistortFunc = inline('x'); % no distortion
%phaseDistortFunc = inline('abs(sin(2*pi*x))');
%phaseDistortFunc = inline('exp(20*x)./exp(20)');
% ------ waveshape function
waveshapeFunc = inline('x');
%waveshapeFunc = inline('exp(3*t) * 2 ./ exp(3) - 1');
%waveshapeFunc = inline('exp( sin(2*pi*t) .* t ) * 2 ./ exp(1) - 1');
% -------------------------------------------------------------------------
% parameters are collected here in a struct. fields can be omitted, the
% generate function has defaults for all of them.
% -------------------------------------------------------------------------
funcs = struct('frequency', frequencyFunc, ...
'shape', shapeFunc, ...
'amplitude', amplitudeFunc, ...
'phasedistort', phaseDistortFunc, ...
'waveshape', waveshapeFunc );
% -------------------------------------------------------------------------
% generate wave from given functions
% -------------------------------------------------------------------------
[wave,time] = generateWave(funcs, samplerate, seconds);
% -------------------------------------------------------------------------
% "reports"
% -------------------------------------------------------------------------
if doPlot
plot(time, wave);
end
if doPlay
sound(wave, samplerate);
end
if doSave
wavwrite(wave, samplerate, 16, filename)
end
% -------------------------------------------------------------------------
% generateWave.m
%
% Author: Martin Alnæs
%
% Licence: Do whatever you want with this. I don't care. I take no
% responsibility whatsoever for anything that might happen.
% Take care of your ears :)
%
% -------------------------------------------------------------------------
%
% function [wave,time] = generateWave(funcs, samplerate, seconds)
%
% - wave will contain a vector with the sample data, range [-1, +1]
% - time will contain a vector with the time in seconds corresponding to each
% sample in wave, convenient for plotting for example.
% - funcs is a struct containing functions defining the wave.
% Fields can be omitted, default functions are defined.
% Description of the functions follow.
%
% ------ frequency function
% - takes a vector of time values in seconds
% - returns wave frequency in Hz for each time step
%
% ------ amplitude function
% - takes a vector of time values in seconds
% - returns wave amplitude for each time step
%
% ------ shape function
% - takes a vector of phases x in [0, 1]
% - returns the shape amplitudes in [-1,1] for each phase value
%
% ------ phasedistortion function
% - takes a vector of phases x in [0, 1]
% - returns distorted phases in [0, 1]
% - should result in the same wavelength
%
% ------ waveshape function
% - takes a vector of sample amplitude values
% - returns new amplitudes for each sample
function [wave,time] = generateWave(funcs, samplerate, seconds)
% basic default functions
if ~isfield(funcs, 'frequency')
setfield(funcs, 'frequency', inline('440')); % constant 440Hz
end
if ~isfield(funcs, 'amplitude')
setfield(funcs, 'amplitude', inline('1')); % constant full amplitude
end
if ~isfield(funcs, 'shape')
setfield(funcs, 'shape', inline('sin(2*pi*x)')); % sine wave
end
if ~isfield(funcs, 'phasedistort')
setfield(funcs, 'phasedistort', inline('x')); % no distortion
end
if ~isfield(funcs, 'waveshape')
setfield(funcs, 'waveshape', inline('x')); % no waveshaping
end
% generate discrete time values (in seconds)
time = linspace(0, seconds, seconds*samplerate);
wave = funcs.waveshape( ...
funcs.amplitude(time) .* ...
funcs.shape( ...
funcs.phasedistort( ...
mod(time .* funcs.frequency(time), 1) ...
) ...
) ...
);
% remove DC offset
offset = sum(wave) / length(wave);
wave = wave - offset;
% normalize
m = max(abs(max(wave)), abs(min(wave)));
wave = wave ./ m;
% done