/*
* Copyright 2010 Jan Schmidt-Reinisch
*
* SoundComp - a sound processing library
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; in version 2.1
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
/**
* This test is there for experimenting with temporary ideas of
* what elements a Control logic could need.
* It will not use the parser, instead attempt to use control logic
* classes "by hand" to create a more complex sound compilation.
*
* On that way, it may also serve as demonstration of how DSP objects
* may be used and combined in practice.
*
* It is not yet functional.
*/
package de.maramuse.soundcomp.test;
import static de.maramuse.soundcomp.process.StandardParameters.*;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import junit.framework.TestCase;
import de.maramuse.soundcomp.events.NoteBirthEvent;
import de.maramuse.soundcomp.events.NoteDeathEvent;
import de.maramuse.soundcomp.events.NoteGateOffEvent;
import de.maramuse.soundcomp.events.NoteGateOnEvent;
import de.maramuse.soundcomp.files.FileFormats;
import de.maramuse.soundcomp.files.InputFile;
import de.maramuse.soundcomp.files.OutputFile;
import de.maramuse.soundcomp.filter.StateVariableFilter;
import de.maramuse.soundcomp.generator.Envelope;
import de.maramuse.soundcomp.generator.PWM;
import de.maramuse.soundcomp.generator.PinkNoise;
import de.maramuse.soundcomp.generator.SawTooth;
import de.maramuse.soundcomp.generator.Sine;
import de.maramuse.soundcomp.math.mul;
import de.maramuse.soundcomp.math.plus;
import de.maramuse.soundcomp.process.ConnectionStore;
import de.maramuse.soundcomp.process.ConstStream;
import de.maramuse.soundcomp.process.Event;
import de.maramuse.soundcomp.process.Mixer;
import de.maramuse.soundcomp.process.ProcessElement;
import de.maramuse.soundcomp.process.Scale;
import de.maramuse.soundcomp.process.StandardParameters;
import de.maramuse.soundcomp.process.Time;
import de.maramuse.soundcomp.process.TypeMismatchException;
import de.maramuse.soundcomp.process.UnknownConnectionException;
import de.maramuse.soundcomp.process.WellTemperedScale;
import de.maramuse.soundcomp.util.AdvancerRegistry;
import de.maramuse.soundcomp.util.GlobalParameters;
public class ComplexTest extends TestCase {
private final static int fileBufferSize=8192;
private static long nameCount=0;
private static final double totalVolume=0.2;
private Object hihatsync=new Object();
GlobalParameters globalParameters=new GlobalParameters(44100);
AdvancerRegistry advancerRegistry=new AdvancerRegistry();
double endTime=0;
private Scale scale=new WellTemperedScale();
private ConnectionStore hihatcl, hihatcr;
private double tempo=80.0/4.0/60.0; // 80 quarter beats per 60 seconds
Mixer solo_L, solo_R, pad_L, pad_R, bass_L, bass_R, drums_L, drums_R;
private enum Drumnote {
HIHAT_CLOSED, HIHAT_OPEN,
BASS_1, BASS_2,
SNARE, RIDE, CRASH,
TOM_HI, TOM_HIMID, TOM_LOMID, TOM_LO;
}
private double getTimestamp(double note16) {
return 0.3+note16/16.0/tempo;
}
private Event getSnare(double left, double right) {
// create the snare drum from a sampled file
Event snare=new Event();
mul m3=new mul(), m4=new mul();
InputFile ifsn=new InputFile("javasrc/de/maramuse/soundcomp/test/testfiles/sd.wav");
snare.setAbstractName("snare");
snare.setInstanceName("snare"+nameCount++);
snare.setDuration(0.025);
snare.setLivetime(1.25);
snare.addElement(ifsn);
try{
m3.setSource(IN_IMAG.i, ifsn, OUT.i);
m3.setSource(IN.i, ConstStream.c(left), OUT.i);
m4.setSource(IN_IMAG.i, ifsn, OUT_IMAG.i);
m4.setSource(IN.i, ConstStream.c(right), OUT.i);
}catch(Exception ex){
ex.printStackTrace();
}
snare.addElement(m3);
snare.addElement(m4);
snare.addMixerConnection(drums_L, m3, OUT.i);
snare.addMixerConnection(drums_R, m4, OUT.i);
return snare;
}
private Event getCrash(double left, double right) {
// create the crash cymbal
// TODO a crash cymbal, when hit multiple times, does not create overlaying sounds, the older sound
// would need to be stopped similar to the hihat handling
Event crash=new Event();
InputFile ifsn=new InputFile("javasrc/de/maramuse/soundcomp/test/testfiles/crash.wav");
crash.setDuration(0.025);
crash.setLivetime(5.25);
crash.addElement(ifsn);
crash.setAbstractName("crash");
crash.setInstanceName("crash"+nameCount++);
return crash;
}
private Event getRide(double left, double right) {
// create the ride cymbal
// TODO a ride cymbal is played in multiple, clearly distinct ways
// TODO a ride cymbal, when hit multiple times, does not create simple overlaying sounds, the older sound
// would need to be influenced on a second hit depending on what kind of hit occurs
Event ride=new Event();
InputFile ifsn=new InputFile("javasrc/de/maramuse/soundcomp/test/testfiles/ride.wav");
ride.setDuration(0.025);
ride.setLivetime(5.25);
ride.addElement(ifsn);
ride.setAbstractName("ride");
ride.setInstanceName("ride"+nameCount++);
return ride;
}
private Event getTom(double left, double right) {
// create a tom.
// a pitched percussive sound with decreasing pitch and harmonics that fade out
// faster than the fundamental.
Event tom=new Event();
ProcessElement ev=new Envelope();
ProcessElement m=new mul();
ProcessElement m1=new mul();
ProcessElement m2=new mul();
ProcessElement p1=new plus();
ProcessElement p2=new plus();
ProcessElement st=new SawTooth();
try{
ev.setSource(SYNC.i, tom, GATE.i);
ev.setSource(A.i, ConstStream.c(0.01), OUT.i);
ev.setSource(D.i, ConstStream.c(1), OUT.i);
ev.setSource(R.i, ConstStream.c(1), OUT.i);
ev.setSource(S.i, ConstStream.c(0), OUT.i);
m.setSource(IN_IMAG.i, ev, OUT.i);
m.setSource(IN.i, st, OUT.i);
m1.setSource(IN_IMAG.i, ev, OUT.i);
m1.setSource(IN.i, ConstStream.c(20), OUT.i);
p1.setSource(IN_IMAG.i, m1, OUT.i);
p1.setSource(IN.i, ConstStream.c(150), OUT.i);
st.setSource(FREQUENCY.i, p1, OUT.i);
p2.setSource(IN_IMAG.i, m2, OUT.i);
m2.setSource(IN.i, ConstStream.c(0.3), OUT.i);
m2.setSource(IN_IMAG.i, ev, OUT.i);
p2.setSource(IN.i, ConstStream.c(0.5), OUT.i);
st.setSource(DUTYCYCLE.i, p2, OUT.i);
}catch(UnknownConnectionException e){
e.printStackTrace();
}catch(TypeMismatchException e){
e.printStackTrace();
}
tom.addElement(ev);
tom.addElement(m);
tom.addElement(m1);
tom.addElement(m2);
tom.addElement(p1);
tom.addElement(p2);
tom.addElement(st);
tom.setAbstractName("tom");
tom.setInstanceName("tom"+nameCount++);
return tom;
}
private Event getBassDrum(double left, double right) {
Event bd=new Event();
bd.setAbstractName("bass drum");
bd.setInstanceName("bass drum"+nameCount++);
bd.setDuration(0.1);
bd.setLivetime(0.5);
ProcessElement ev=new Envelope();
ProcessElement m=new mul();
ProcessElement m1=new mul();
ProcessElement m2=new mul();
ProcessElement p1=new plus();
ProcessElement p2=new plus();
ProcessElement st=new SawTooth();
ProcessElement m3=new mul();
ProcessElement m4=new mul();
try{
ev.setSource(SYNC.i, bd, GATE.i);
ev.setSource(A.i, ConstStream.c(0.01), OUT.i);
ev.setSource(D.i, ConstStream.c(0.7), OUT.i);
ev.setSource(R.i, ConstStream.c(0.7), OUT.i);
ev.setSource(S.i, ConstStream.c(0), OUT.i);
m.setSource(IN_IMAG.i, ev, OUT.i);
m.setSource(IN.i, st, OUT.i);
m1.setSource(IN_IMAG.i, ev, OUT.i);
m1.setSource(IN.i, ConstStream.c(20), OUT.i);
p1.setSource(IN_IMAG.i, m1, OUT.i);
p1.setSource(IN.i, ConstStream.c(50), OUT.i);
st.setSource(FREQUENCY.i, p1, OUT.i);
p2.setSource(IN_IMAG.i, m2, OUT.i);
m2.setSource(IN.i, ConstStream.c(0.3), OUT.i);
m2.setSource(IN_IMAG.i, ev, OUT.i);
p2.setSource(IN.i, ConstStream.c(0.5), OUT.i);
st.setSource(DUTYCYCLE.i, p2, OUT.i);
bd.addElement(ev);
bd.addElement(m);
bd.addElement(m1);
bd.addElement(m2);
bd.addElement(p1);
bd.addElement(p2);
bd.addElement(st);
bd.addElement(m3);
bd.addElement(m4);
m3.setSource(IN_IMAG.i, m, OUT.i);
m3.setSource(IN.i, ConstStream.c(left), OUT.i);
m4.setSource(IN_IMAG.i, m, OUT.i);
m4.setSource(IN.i, ConstStream.c(right), OUT.i);
}catch(UnknownConnectionException e){
e.printStackTrace();
}catch(TypeMismatchException e){
e.printStackTrace();
}
bd.addMixerConnection(drums_L, m3, OUT.i);
bd.addMixerConnection(drums_R, m4, OUT.i);
return bd;
}
private Event getHHOpen(double left, double right) {
// create the open hihat from a sampled file
synchronized(hihatsync){
Event hihat_open=new Event();
InputFile ifho=new InputFile("javasrc/de/maramuse/soundcomp/test/testfiles/hh-o.wav");
hihat_open.setDuration(0.025);
hihat_open.setLivetime(1.25);
hihat_open.addElement(ifho);
/*
* we would have to disregister the open hihat when it is hit closed, but not at creation time, rather at run
* time. if(hihat!=null){ hihat.die(); drums_L.disconnect(hihatcl); drums_R.disconnect(hihatcr); }
*/
hihat_open.setAbstractName("hihat_open");
hihat_open.setInstanceName("hihat_open"+nameCount++);
return hihat=hihat_open;
}
}
private Event getHHClosed(double left, double right) {
// create the closed hihat from a sampled file
synchronized(hihatsync){
Event hihat_closed=new Event();
mul m3=new mul(), m4=new mul();
InputFile ifhc=new InputFile("javasrc/de/maramuse/soundcomp/test/testfiles/hh-c.wav");
ifhc.setAbstractName("ifhc");
ifhc.setInstanceName("ifhc"+nameCount++);
hihat_closed.setDuration(0.025);
hihat_closed.setLivetime(0.5);
/*
* we would have to disregister the open hihat when it is hit closed, but not at creation time, rather at run
* time. if(hihat!=null){ hihat.die(); drums_L.disconnect(hihatcl); drums_R.disconnect(hihatcr); }
*/
hihat_closed.setAbstractName("hihat_closed");
hihat_closed.setInstanceName("hihat_closed"+nameCount++);
try{
m3.setSource(IN_IMAG.i, ifhc, OUT.i);
m3.setSource(IN.i, ConstStream.c(left), OUT.i);
m4.setSource(IN_IMAG.i, ifhc, OUT.i);
m4.setSource(IN.i, ConstStream.c(right), OUT.i);
}catch(Exception ex){
ex.printStackTrace();
}
hihat_closed.addElement(ifhc);
hihat_closed.addElement(m3);
hihat_closed.addElement(m4);
hihat_closed.addMixerConnection(drums_L, m3, OUT.i);
hihat_closed.addMixerConnection(drums_R, m4, OUT.i);
return hihat=hihat_closed;
}
}
private Event hihat=null;
// create one note instance for the solo instrument
private Event createSoloNote(note n, double volume) {
double effvol=volume;
if(n.beats<1.0/8)effvol*=0.7;
Event event=new Event();
SawTooth st=new SawTooth();
PinkNoise pn=new PinkNoise();
mul noisevol=new mul();
mul sawvol=new mul();
Envelope noiseenv=new Envelope();
Envelope sawenv=new Envelope();
Mixer out=new Mixer();
try{
st.setSource(FREQUENCY.i, ConstStream.c(n.frequency), OUT.i);
st.setSource(DUTYCYCLE.i, ConstStream.c(0.5), OUT.i);
out.setSource(-1, sawvol, OUT.i);
out.setSource(-1, ConstStream.c(0.5*effvol), OUT.i);
out.setSource(-2, noisevol, OUT.i);
out.setSource(-2, ConstStream.c(0.4*effvol), OUT.i);
sawvol.setSource(IN_IMAG.i, st, OUT.i);
noisevol.setSource(IN_IMAG.i, pn, OUT.i);
sawvol.setSource(IN.i, sawenv, OUT.i);
noisevol.setSource(IN.i, noiseenv, OUT.i);
noiseenv.setSource(SYNC.i, event, GATE.i);
sawenv.setSource(SYNC.i, event, GATE.i);
noiseenv.setSource(A.i, ConstStream.c(0.02), OUT.i);
noiseenv.setSource(D.i, ConstStream.c(0.03), OUT.i);
noiseenv.setSource(S.i, ConstStream.c(0.1), OUT.i);
sawenv.setSource(A.i, ConstStream.c(0.02), OUT.i);
sawenv.setSource(D.i, ConstStream.c(1), OUT.i);
if(n.legato){
sawenv.setSource(S.i, ConstStream.c(0), OUT.i);
sawenv.setSource(R.i, ConstStream.c(1), OUT.i);
noiseenv.setSource(R.i, ConstStream.c(1), OUT.i);
}else{
sawenv.setSource(S.i, ConstStream.c(0.2), OUT.i);
sawenv.setSource(R.i, ConstStream.c(0.01), OUT.i);
noiseenv.setSource(R.i, ConstStream.c(0.01), OUT.i);
}
}catch(UnknownConnectionException e){
e.printStackTrace();
}catch(TypeMismatchException e){
e.printStackTrace();
}
double ts=n.startbeat/tempo;
event.setStarttime(ts);
if(n.legato){
event.setDuration(n.beats/tempo);
}else{
event.setDuration(Math.min(n.beats, 1.0/4.0)/tempo*0.6);
}
event.setLivetime(event.getDuration()+1);
event.setFrequency(n.frequency);
// System.out.println("solo note "+n.frequency+"Hz at "+ts+" for "+event.getDuration()+"/"+event.getLivetime()+"(beats:"+n.beats+","+tempo+")");
event.setPitch(n.pitch);
event.addElement(st);
event.addElement(pn);
event.addElement(noisevol);
event.addElement(noiseenv);
event.addElement(sawvol);
event.addElement(sawenv);
event.addElement(out);
NoteGateOffEvent ng0e=new NoteGateOffEvent(event, ts+event.getDuration(), advancerRegistry);
NoteBirthEvent nbe=new NoteBirthEvent(event, ts, advancerRegistry);
NoteGateOnEvent ng1e=new NoteGateOnEvent(event, ts+1.01/globalParameters.getSampleRate(),
advancerRegistry);
NoteDeathEvent nde=new NoteDeathEvent(event, ts+event.getLivetime(), advancerRegistry);
event.addMixerConnection(solo_L, out, OUT.i);
event.addMixerConnection(solo_R, out, OUT.i);
advancerRegistry.addEvent(nbe.getTimestamp(), nbe, event);
advancerRegistry.addEvent(ng0e.getTimestamp(), ng0e, event);
advancerRegistry.addEvent(ng1e.getTimestamp(), ng1e, event);
advancerRegistry.addEvent(nde.getTimestamp(), nde, event);
return event;
}
// create one note instance for the bass instrument
private Event createBassNote(String note, double ts, double durationGate, double durationTotal,
double volume) {
Event event=new Event();
event.setAbstractName("bass");
event.setInstanceName("bass"+nameCount++);
event.setStarttime(ts);
event.setDuration(durationGate);
event.setLivetime(durationTotal);
event.setFrequency(scale.getValueFor(note));
event.setPitch(scale.getPitch(note));
SawTooth st=new SawTooth();
st.setAbstractName("bass-st");
st.setInstanceName("bass-st"+nameCount++);
Envelope filterEnv=new Envelope();
Envelope volumeEnv=new Envelope();
StateVariableFilter svf=new StateVariableFilter();
mul mfilfreq=new mul(), mfilfreq2=new mul();
plus pfilfreq=new plus();
mul mout=new mul();
mul mvol=new mul();
try{
filterEnv.setSource(SYNC.i, event, GATE.i);
filterEnv.setSource(A.i, ConstStream.c(0.2), OUT.i);
filterEnv.setSource(D.i, ConstStream.c(0.4), OUT.i);
filterEnv.setSource(R.i, ConstStream.c(0.4), OUT.i);
filterEnv.setSource(S.i, ConstStream.c(0), OUT.i);
volumeEnv.setSource(SYNC.i, event, GATE.i);
volumeEnv.setSource(A.i, ConstStream.c(0.1), OUT.i);
volumeEnv.setSource(D.i, ConstStream.c(0.3), OUT.i);
volumeEnv.setSource(R.i, ConstStream.c(0.3), OUT.i);
volumeEnv.setSource(S.i, ConstStream.c(0.3), OUT.i);
st.setSource(FREQUENCY.i, event, FREQUENCY.i);
svf.setSource(IN.i, st, OUT.i);
mfilfreq.setSource(IN.i, filterEnv, OUT.i);
mfilfreq.setSource(IN_IMAG.i, ConstStream.c(4), OUT.i);
mfilfreq2.setSource(IN.i, mfilfreq, OUT.i);
mfilfreq2.setSource(IN_IMAG.i, event, FREQUENCY.i);
pfilfreq.setSource(IN_IMAG.i, mfilfreq2, OUT.i);
pfilfreq.setSource(IN.i, event, FREQUENCY.i);
svf.setSource(FREQUENCY.i, pfilfreq, OUT.i);
svf.setSource(Q.i, ConstStream.c(0.9), OUT.i);
mout.setSource(IN_IMAG.i, volumeEnv, OUT.i);
mout.setSource(IN.i, mvol, OUT.i);
mvol.setSource(IN_IMAG.i, svf, LP.i);
mvol.setSource(IN.i, ConstStream.c(volume), OUT.i);
}catch(UnknownConnectionException e){
e.printStackTrace();
}catch(TypeMismatchException e){
e.printStackTrace();
}
event.addElement(st);
event.addElement(filterEnv);
event.addElement(volumeEnv);
event.addElement(mout);
event.addElement(mvol);
event.addElement(svf);
event.addElement(pfilfreq);
event.addElement(mfilfreq);
event.addElement(mfilfreq2);
NoteGateOffEvent nge=new NoteGateOffEvent(event, ts+event.getDuration(), advancerRegistry);
NoteBirthEvent nbe=new NoteBirthEvent(event, ts, advancerRegistry);
NoteGateOnEvent ng1e=new NoteGateOnEvent(event, ts+1.01/globalParameters.getSampleRate(),
advancerRegistry);
NoteDeathEvent nde=new NoteDeathEvent(event, ts+event.getLivetime(), advancerRegistry);
event.addMixerConnection(bass_L, mout, OUT.i);
event.addMixerConnection(bass_R, mout, OUT.i);
advancerRegistry.addEvent(nbe.getTimestamp(), nbe, event);
advancerRegistry.addEvent(nge.getTimestamp(), nge, event);
advancerRegistry.addEvent(ng1e.getTimestamp(), ng1e, event);
advancerRegistry.addEvent(nde.getTimestamp(), nde, event);
return event;
}
// create one note instance for the pad instrument
private Event createPadNote(String note, double ts, double durationGate, double durationTotal,
double volume) {
Event event=new Event();
event.setAbstractName("pad");
event.setInstanceName("pad"+nameCount++);
event.setStarttime(ts);
event.setDuration(durationGate);
event.setLivetime(durationTotal);
event.setFrequency(scale.getValueFor(note));
event.setPitch(scale.getPitch(note));
PWM pwm=new PWM();
pwm.setAbstractName("pad-pw");
pwm.setInstanceName("pad-pw"+nameCount++);
Envelope filterEnv=new Envelope();
Sine lfo=new Sine();
Envelope volumeEnv=new Envelope();
StateVariableFilter svf=new StateVariableFilter();
StateVariableFilter hp=new StateVariableFilter();
mul mfilfreq=new mul(), mfilfreq2=new mul();
plus pfilfreq=new plus();
mul mout=new mul();
mul lfof=new mul();
mul mvol=new mul();
mul dmul=new mul();
mul dmul2=new mul();
plus dpl=new plus();
plus dpl2=new plus();
try{
lfof.setSource(IN_IMAG.i, event, FREQUENCY.i);
lfof.setSource(IN.i, ConstStream.c(0.005), OUT.i);
lfo.setSource(FREQUENCY.i, lfof, OUT.i);
hp.setSource(FREQUENCY.i, ConstStream.c(20), OUT.i);
hp.setSource(IN.i, pwm, OUT.i);
filterEnv.setSource(SYNC.i, event, GATE.i);
filterEnv.setSource(A.i, ConstStream.c(0.4), OUT.i);
filterEnv.setSource(D.i, ConstStream.c(0.7), OUT.i);
filterEnv.setSource(R.i, ConstStream.c(0.7), OUT.i);
filterEnv.setSource(S.i, ConstStream.c(0), OUT.i);
volumeEnv.setSource(SYNC.i, event, GATE.i);
volumeEnv.setSource(A.i, ConstStream.c(0.4), OUT.i);
volumeEnv.setSource(D.i, ConstStream.c(1.1), OUT.i);
volumeEnv.setSource(R.i, ConstStream.c(1.1), OUT.i);
volumeEnv.setSource(S.i, ConstStream.c(0.8), OUT.i);
pwm.setSource(FREQUENCY.i, event, FREQUENCY.i);
dmul.setSource(IN_IMAG.i, filterEnv, OUT.i);
dmul.setSource(IN.i, ConstStream.c(0.4), OUT.i);
dpl.setSource(IN_IMAG.i, dmul, OUT.i);
dpl.setSource(IN.i, ConstStream.c(0.5), OUT.i);
pwm.setSource(DUTYCYCLE.i, dpl2, OUT.i);
dpl2.setSource(IN.i, dpl, OUT.i);
dpl2.setSource(IN_IMAG.i, dmul2, OUT.i);
dmul2.setSource(IN_IMAG.i, lfo, OUT.i);
dmul2.setSource(IN.i, ConstStream.c(0.2), OUT.i);
svf.setSource(IN.i, hp, HP.i);
mfilfreq.setSource(IN.i, filterEnv, OUT.i);
mfilfreq.setSource(IN_IMAG.i, ConstStream.c(4), OUT.i);
mfilfreq2.setSource(IN.i, mfilfreq, OUT.i);
mfilfreq2.setSource(IN_IMAG.i, event, FREQUENCY.i);
pfilfreq.setSource(IN_IMAG.i, mfilfreq2, OUT.i);
pfilfreq.setSource(IN.i, event, FREQUENCY.i);
svf.setSource(FREQUENCY.i, pfilfreq, OUT.i);
svf.setSource(Q.i, ConstStream.c(0.9), OUT.i);
mout.setSource(IN_IMAG.i, volumeEnv, OUT.i);
mout.setSource(IN.i, mvol, OUT.i);
mvol.setSource(IN_IMAG.i, svf, LP.i);
mvol.setSource(IN.i, ConstStream.c(volume*totalVolume), OUT.i);
}catch(UnknownConnectionException e){
e.printStackTrace();
}catch(TypeMismatchException e){
e.printStackTrace();
}
event.addElement(pwm);
event.addElement(filterEnv);
event.addElement(volumeEnv);
event.addElement(mout);
event.addElement(mvol);
event.addElement(svf);
event.addElement(hp);
event.addElement(pfilfreq);
event.addElement(mfilfreq);
event.addElement(mfilfreq2);
event.addElement(dmul);
event.addElement(dmul2);
event.addElement(dpl);
event.addElement(lfo);
event.addElement(lfof);
event.addElement(dpl2);
NoteGateOffEvent nge=new NoteGateOffEvent(event, ts+event.getDuration(), advancerRegistry);
NoteBirthEvent nbe=new NoteBirthEvent(event, ts, advancerRegistry);
NoteGateOnEvent ng1e=new NoteGateOnEvent(event, ts+1.01/globalParameters.getSampleRate(),
advancerRegistry);
NoteDeathEvent nde=new NoteDeathEvent(event, ts+event.getLivetime(), advancerRegistry);
event.addMixerConnection(pad_L, mout, OUT.i);
event.addMixerConnection(pad_R, mout, OUT.i);
advancerRegistry.addEvent(nbe.getTimestamp(), nbe, event);
advancerRegistry.addEvent(nge.getTimestamp(), nge, event);
advancerRegistry.addEvent(ng1e.getTimestamp(), ng1e, event);
advancerRegistry.addEvent(nde.getTimestamp(), nde, event);
return event;
}
// create one EventContainer note instance for the drums instrument. opposed to the other instruments,
// the sound generating object is not instantiated per note, as some instruments (cymbals, hihat) are
// strictly monophonous, and the two hihat sounds even mutually exclusive.
// therefore a "shell object" for each note acts as proxy to the singleton generator and handles timing
// and mutual exclusion, whereas the singleton generators handle sound generation
private Event createDrumNote(Drumnote note, double ts, double volume) {
Event event;
//boolean hihat_=false;
double lvolume=volume*totalVolume;
switch(note){
case BASS_1:
event=getBassDrum(0.7*lvolume, 0.6*lvolume);
event.setFrequency(70);
break;
case BASS_2:
event=getBassDrum(0.6*lvolume, 0.7*lvolume);
event.setFrequency(77);
break;
case CRASH:
event=getCrash(0.2*lvolume, 0.7*lvolume);
break;
case HIHAT_CLOSED:
// if a hihat is hit in closed state, it is not permissible for
// the hihat to still sound on as open. of course, this is an
// extremely basic simulation - the process of closing the sounding
// hihat is completely ignored.
event=getHHClosed(0.5*lvolume, 0.5*lvolume);
//hihat_=true;
break;
case HIHAT_OPEN:
event=getHHOpen(0.5*lvolume, 0.5*lvolume);
//hihat_=true;
break;
case RIDE:
event=getRide(0.8*lvolume, 0.2*lvolume);
break;
default: // which case should that be?
case SNARE:
event=getSnare(0.45*lvolume, 0.55*lvolume);
break;
case TOM_HI:
event=getTom(0.4*lvolume, 0.6*lvolume);
event.setFrequency(130);
break;
case TOM_HIMID:
event=getTom(0.5*lvolume, 0.5*lvolume);
event.setFrequency(120);
break;
case TOM_LOMID:
event=getTom(0.6*lvolume, 0.4*lvolume);
event.setFrequency(110);
break;
case TOM_LO:
event=getTom(0.7*lvolume, 0.3*lvolume);
event.setFrequency(90);
break;
}
event.setStarttime(ts);
event.setPitch(Double.NaN);
NoteGateOffEvent nge=new NoteGateOffEvent(event, ts+event.getDuration(), advancerRegistry);
NoteBirthEvent nbe=new NoteBirthEvent(event, ts, advancerRegistry);
NoteGateOnEvent ng1e=new NoteGateOnEvent(event, ts+1.01/globalParameters.getSampleRate(),
advancerRegistry);
NoteDeathEvent nde=new NoteDeathEvent(event, ts+event.getLivetime(), advancerRegistry);
advancerRegistry.addEvent(nbe, event);
advancerRegistry.addEvent(nge, event);
advancerRegistry.addEvent(ng1e, event);
advancerRegistry.addEvent(nde, event);
return event;
}
// create the frame structure for the bass string instrument
private void createBass() {
bass_L=new Mixer();
bass_L.setAbstractName("bass_L");
bass_L.setInstanceName("bass_L");
advancerRegistry.registerAdvancer(bass_L);
bass_R=new Mixer();
bass_R.setAbstractName("bass_R");
bass_R.setInstanceName("bass_R");
advancerRegistry.registerAdvancer(bass_R);
for(int t=0; t<400; t++){
double ts=getTimestamp(t);
switch(t%16){
case 0:
createBassNote("A1", ts, 0.2/tempo, 0.7/tempo, 0.8);
break;
case 1:
break;
case 2:
break;
case 3:
break;
case 4:
createBassNote("F1", ts, 0.2/tempo, 0.7/tempo, 0.8);
break;
case 5:
break;
case 6:
break;
case 7:
break;
case 8:
createBassNote("G1", ts, 0.2/tempo, 0.7/tempo, 1);
break;
case 9:
break;
case 10:
break;
case 11:
break;
case 12:
createBassNote("C1", ts, 0.1/tempo, 0.7/tempo, 1);
break;
case 13:
break;
case 14:
createBassNote("E1", ts, 0.2/tempo, 0.7/tempo, 1);
break;
case 15:
break;
}
}
}
static double nextsolobeat=Double.NaN;
static double nextsolobeats=Double.NaN;
private class note {
double startbeat;
double frequency;
double beats;
boolean legato;
double pitch;
public note(int bar, double startbeat, String n, double beats, boolean legato) {
this.startbeat=bar+startbeat/16;
this.frequency=scale.getValueFor(n);
this.pitch=scale.getPitch(n);
this.beats=beats/16;
this.legato=legato;
nextsolobeats=this.beats;
nextsolobeat=this.beats+this.startbeat;
}
public note(int bar, double startbeat, String n, double beats) {
this.startbeat=bar+startbeat/16;
this.frequency=scale.getValueFor(n);
this.pitch=scale.getPitch(n);
this.beats=beats/16;
this.legato=false;
nextsolobeats=this.beats;
nextsolobeat=this.beats+this.startbeat;
}
public note(String n, double beats) {
this.startbeat=nextsolobeat;
this.frequency=scale.getValueFor(n);
this.pitch=scale.getPitch(n);
this.beats=beats/16;
nextsolobeats=this.beats;
nextsolobeat+=nextsolobeats;
this.legato=false;
}
public note(String n) {
this.startbeat=nextsolobeat;
this.frequency=scale.getValueFor(n);
this.pitch=scale.getPitch(n);
this.beats=nextsolobeats;
nextsolobeat+=nextsolobeats;
this.legato=false;
}
}
// create the frame structure for the solo instrument
private void createSolo() {
note[] notes=new note[]{
new note(0, 15, "g1", 1),
new note("a1"),
new note("b1"),
new note("c2"),
new note("d2"),
new note("e2", 8),
new note(1, 9, "g2", 1),
new note("f2", 2.0/3),
new note("g2"),
new note("f2"),
new note("e2"),
new note("f2"),
new note("e2"),
new note("d2"),
new note("e2"),
new note("d2"),
new note("c2", 8),
new note(2, 15, "g1", 1),
new note("a1"),
new note("b1"),
new note("c2"),
new note("d2"),
new note("e2", 8),
new note(3, 9, "g2", 1),
new note("f2", 2.0/3),
new note("g2"),
new note("f2"),
new note("e2"),
new note("f2"),
new note("e2"),
new note("d2"),
new note("e2"),
new note("d2"),
new note("c2", 8),
new note(4, 15, "g1", 1),
new note("a1"),
new note("b1"),
new note("c2"),
new note("d2"),
new note("e2", 8),
new note(5, 9, "g2", 1),
new note("f2", 2.0/3),
new note("g2"),
new note("f2"),
new note("e2"),
new note("f2"),
new note("e2"),
new note("d2"),
new note("e2"),
new note("d2"),
new note("c2", 8),
};
solo_L=new Mixer();
solo_L.setAbstractName("solo_L");
solo_L.setInstanceName("solo_L");
solo_R=new Mixer();
solo_R.setAbstractName("solo_R");
solo_R.setInstanceName("solo_R");
advancerRegistry.registerAdvancer(solo_L);
advancerRegistry.registerAdvancer(solo_R);
for(note n:notes){
createSoloNote(n, 0.4);
}
}
// create the frame structure for the pad instrument
private void createPad() {
pad_L=new Mixer();
pad_L.setAbstractName("pad_L");
pad_L.setInstanceName("pad_L");
pad_R=new Mixer();
pad_R.setAbstractName("pad_R");
pad_R.setInstanceName("pad_R");
advancerRegistry.registerAdvancer(pad_L);
advancerRegistry.registerAdvancer(pad_R);
for(int t=0; t<400; t++){
double ts=getTimestamp(t);
switch(t%16){
case 0:
createPadNote("a1", ts, 0.6/tempo, 1.2/tempo, 0.4);
createPadNote("c2", ts, 0.6/tempo, 1.2/tempo, 0.4);
createPadNote("e3", ts, 0.6/tempo, 1.2/tempo, 0.4);
break;
case 1:
break;
case 2:
break;
case 3:
break;
case 4:
break;
case 5:
break;
case 6:
break;
case 7:
break;
case 8:
createPadNote("g1", ts, 0.6/tempo, 1.2/tempo, 0.4);
createPadNote("b1", ts, 0.6/tempo, 1.2/tempo, 0.4);
createPadNote("e3", ts, 0.6/tempo, 1.2/tempo, 0.4);
break;
case 9:
break;
case 10:
break;
case 11:
break;
case 12:
break;
case 13:
break;
case 14:
break;
case 15:
break;
}
}
}
// create the frame structure for the drums instrument, and all the monophonous singleton instruments
private void createDrumset() throws UnknownConnectionException, TypeMismatchException {
// noise generation is handled globally. maybe not optimal, but good enough for this demo
drums_R=new Mixer();
drums_R.setAbstractName("drums_R");
drums_R.setInstanceName("drums_R");
advancerRegistry.registerAdvancer(drums_R);
drums_L=new Mixer();
drums_L.setAbstractName("drums_L");
drums_L.setInstanceName("drums_L");
advancerRegistry.registerAdvancer(drums_L);
for(int t=0; t<400; t++){
double ts=getTimestamp(t);
switch(t%16){
case 0:
createDrumNote(Drumnote.BASS_1, ts, 1.0);
break;
case 1:
createDrumNote(Drumnote.HIHAT_CLOSED, ts, 0.3);
break;
case 2:
break;
case 3:
break;
case 4:
break;
case 5:
createDrumNote(Drumnote.HIHAT_CLOSED, ts, 0.3);
break;
case 6:
break;
case 7:
break;
case 8:
createDrumNote(Drumnote.SNARE, ts, 1.0);
break;
case 9:
createDrumNote(Drumnote.HIHAT_CLOSED, ts, 0.3);
break;
case 10:
break;
case 11:
break;
case 12:
break;
case 13:
createDrumNote(Drumnote.HIHAT_CLOSED, ts, 0.3);
break;
case 14:
break;
case 15:
break;
}
}
}
/**
* Creates a test file containing multiple voices with various types of sound generation and manipulation
*/
public void testComplexSong() {
try{
double calcTime=System.currentTimeMillis();
advancerRegistry.clear();
globalParameters.setSampleRate(44100);
Mixer mixL=new Mixer();
mixL.setAbstractName("mixL");
mixL.setInstanceName("mixL");
Mixer mixR=new Mixer();
mixR.setAbstractName("mixR");
mixR.setInstanceName("mixR");
createBass();
createPad();
createSolo();
createDrumset();
System.out.println(""+(System.currentTimeMillis()-calcTime)/1000+": events created");
mixL.setSource(-1, bass_L, OUT.i);
mixL.setSource(-4, drums_L, OUT.i);
mixL.setSource(-2, pad_L, OUT.i);
mixL.setSource(-3, solo_L, OUT.i);
mixR.setSource(-1, bass_R, OUT.i);
mixR.setSource(-4, drums_R, OUT.i);
mixR.setSource(-2, pad_R, OUT.i);
mixR.setSource(-3, solo_R, OUT.i);
OutputFile of=new OutputFile(2);
Time time=new Time();
of.setSource(0, mixL, OUT.i);
of.setSource(-1, mixR, OUT.i);
advancerRegistry.registerAdvancer(time);
advancerRegistry.registerAdvancer(of);
advancerRegistry.registerAdvancer(mixR);
advancerRegistry.registerAdvancer(mixL);
time.reset();
System.out.println(""+(System.currentTimeMillis()-calcTime)/1000+": total song time: "+getTimestamp(104));
double lastTime=0;
for(; time.getValue(StandardParameters.OUT.i)<getTimestamp(103);){
advancerRegistry.advanceAll();
if(time.getValue(OUT.i)>lastTime+1){
lastTime=Math.floor(time.getValue(OUT.i));
System.out.println(""+(System.currentTimeMillis()-calcTime)/1000+": prepared song time: "+time.getValue(OUT.i)+"s");
}
}
try{
File f=new File("demo.wav");
BufferedOutputStream fos=new BufferedOutputStream(new FileOutputStream(f), fileBufferSize);
of.setFormat(FileFormats.FMT_WAVE_S16);
System.out.println(""+(System.currentTimeMillis()-calcTime)/1000+": writing file");
of.write(fos);
fos.close();
}catch(IOException ex){
fail(ex.getMessage());
}
System.out.println(""+(System.currentTimeMillis()-calcTime)/1000+": finished");
}catch(Exception ex){
fail(ex.getMessage());
}
}
public static void main(String[] args) {
new ComplexTest().testComplexSong();
}
}