/** * VocSynth.java * * Voice Synthesizer, 1999 */ /*<Imports>*/ import java.applet.Applet; import java.awt.*; import java.awt.event.*; import sun.audio.*; import soundmodel.Mulaw; /*</Imports>*/ /** * Voice synthesizer models the vocal tract * * @version 0.1 * @author Joel Niederhauser */ class TubeCanvas extends Canvas implements MouseListener,MouseMotionListener // Interface for displaying and changing vocal tract { VocSynth parent; double tract[]; int tlength; public TubeCanvas (VocSynth par,double tract[],int tlength) { parent=par; this.tract=tract; this.tlength=tlength; addMouseListener(this); addMouseMotionListener(this); setBackground(Color.white); } public void paint (Graphics g) { Dimension d = size(); int w=d.width; int h=d.height; //draw frame g.setColor(Color.darkGray); g.drawLine(0,0,w-1,0); g.drawLine(0,0,0,h-1); g.setColor(Color.gray); g.drawLine(w-1,h-1,w-1,0); g.drawLine(w-1,h-1,0,h-1); g.setColor(Color.black); //end of frame for (int i=0;i<tlength;i++) // draw all tubes of vocal tract { g.drawLine(i*w/tlength,h/2-(int)(tract[i]*h/2),(i+1)*w/tlength,h/2-(int)(tract[i]*h/2)); g.drawLine(i*w/tlength,h/2+(int)(tract[i]*h/2),(i+1)*w/tlength,h/2+(int)(tract[i]*h/2)); if (i<tlength-1) { g.drawLine((i+1)*w/tlength,h/2-(int)(tract[i]*h/2),(i+1)*w/tlength,h/2-(int)(tract[i+1]*h/2)); g.drawLine((i+1)*w/tlength,h/2+(int)(tract[i]*h/2),(i+1)*w/tlength,h/2+(int)(tract[i+1]*h/2)); } } } public void mousePressed(MouseEvent e) // adjust crossarea to where mouse clicked { Dimension d = size(); int w=d.width; int h=d.height; int x=e.getX(); int y=e.getY(); if (y>h/2) { tract[x*tlength/w]=(y*2.0-h)/h; } else { tract[x*tlength/w]=(h-2.0*y)/h; } repaint(x-w/tlength-1,0,w*2/tlength+1,h-1); parent.calcresponse(); } public void mouseReleased(MouseEvent e) {} public void mouseEntered(MouseEvent e) {} public void mouseExited(MouseEvent e) {} public void mouseClicked(MouseEvent e) {} public void mouseDragged(MouseEvent e) //drag tubecrossareas with mouse { Dimension d = size(); int w=d.width; int h=d.height; int x=e.getX(); int y=e.getY(); if (x<0) x=0; if (x>w) x=w-1; if (y<1) y=1; if (y>h-2) y=h-2; if (y>h/2) { tract[x*tlength/w]=(y*2.0-h)/h; } else { tract[x*tlength/w]=(h-2.0*y)/h; } repaint(x-w/tlength-1,0,w*2/tlength+1,h-1); parent.calcresponse(); } public void mouseMoved(MouseEvent e) {} } class WaveDraw extends Canvas // draws input and output pulse { Container parent; int w,h,len; Dimension size; boolean trueSizeKnown = true; double buf[]; public WaveDraw (Container par,int iniw, int inih,double buf[],int len) { parent=par; this.len=len; this.buf=buf; w=iniw; h=inih; size=new Dimension(w,h); setBackground(Color.white); } public Dimension getPreferredSize() { return getMinimumSize(); } public Dimension getMinimumSize() { return size; } public void paint (Graphics g) { //g.drawRect(0,0,w-1,h-1); // frame g.setColor(Color.darkGray); g.drawLine(0,0,w-1,0); g.drawLine(0,0,0,h-1); g.setColor(Color.lightGray); g.drawLine(w-1,h-1,w-1,0); g.drawLine(w-1,h-1,0,h-1); g.setColor(Color.black); //end of frame for (int i=0;i<len-1;i++) { g.drawLine((i * w) / len,h/2- (int)(buf[i] *h/2),((i+1)*w) / len,h/2- (int)(buf[i+1] *h/2)); } } } class SPanel extends Panel // Show Lip or Glottisimpedance { Label value; public SPanel(String myTitle) { setLayout(new BorderLayout()); Label label = new Label(myTitle, Label.CENTER); add("Center",label); setBackground(Color.gray); } public void paint(Graphics g) { Dimension d = size(); int w=d.width; int h=d.height; // frame g.setColor(Color.darkGray); g.drawLine(0,0,w-1,0); g.drawLine(0,0,0,h-1); g.setColor(Color.white); g.drawLine(w-1,h-1,w-1,0); g.drawLine(w-1,h-1,0,h-1); //end of frame } public Insets insets() { return new Insets(5,5,5,5); } } class CPanel extends Panel // display all buttons { public CPanel(ActionListener par) { Label l; l=new Label("Presets:"); add(l); Button b = null; //add presetbuttons b=new Button("AH"); b.setActionCommand("AH"); b.addActionListener(par); add(b); b=new Button("EH"); b.setActionCommand("EH"); b.addActionListener(par); add(b); b=new Button("EE"); b.setActionCommand("EE"); b.addActionListener(par); add(b); b=new Button("OH"); b.setActionCommand("OH"); b.addActionListener(par); add(b); b=new Button("OO"); b.setActionCommand("OO"); b.addActionListener(par); add(b); l=new Label("Sound:"); add(l); b = new Button("Start"); b.setActionCommand("start"); b.addActionListener(par); add(b); b = new Button("Stop"); b.setActionCommand("stop"); b.addActionListener(par); add(b); setBackground(Color.gray); } public void paint(Graphics g) { Dimension d = size(); int w=d.width; int h=d.height; // frame g.setColor(Color.darkGray); g.drawLine(0,0,w-1,0); g.drawLine(0,0,0,h-1); g.setColor(Color.white); g.drawLine(w-1,h-1,w-1,0); g.drawLine(w-1,h-1,0,h-1); //end of frame } public Insets insets() { return new Insets(5,5,5,5); } } class WPanel extends Panel // frame for two wavediplayareas { WaveDraw inw,outw; public WPanel(double input[],int lenin, double output[],int lenout) { inw=new WaveDraw(this,200,64,input,lenin); outw=new WaveDraw(this,200,64,output,lenout); Label l; l=new Label("Input:"); add(l); add(inw); l=new Label("Output:"); add(l); add(outw); setBackground(Color.gray); } public void paint(Graphics g) { Dimension d = size(); int w=d.width; int h=d.height; // frame g.setColor(Color.darkGray); g.drawLine(0,0,w-1,0); g.drawLine(0,0,0,h-1); g.setColor(Color.white); g.drawLine(w-1,h-1,w-1,0); g.drawLine(w-1,h-1,0,h-1); //end of frame } public Insets insets() { return new Insets(5,5,5,5); } } public class VocSynth extends Applet implements ActionListener { AudioData sampleData; ContinuousAudioDataStream sampleStream; byte sample[]; //array for playing sound double inpwave[]; //glottal wave double outwave[]; //lip wave int length=64; //samples per double tract[]; //cross areas of tubes int tlength=8; //no of tubes boolean playing=false; TubeCanvas tubecan; WPanel wpan; CPanel pan; SPanel glotslider,lipslider; public void init() { sample=new byte[length]; inpwave=new double[length]; outwave=new double[length]; tract=new double[tlength]; //tract[0]=.5;tract[1]=.3;tract[2]=.2;tract[3]=.6;tract[4]=.9;tract[5]=.6;tract[6]=.2;tract[7]=.9; sampleData=new AudioData(sample); sampleStream=new ContinuousAudioDataStream(sampleData); setLayout(new BorderLayout(5,5)); tubecan=new TubeCanvas(this,tract,tlength); wpan=new WPanel(inpwave,length,outwave,length); pan=new CPanel(this); glotslider=new SPanel("Zglo=0"); lipslider=new SPanel("Zl=Inf."); add("North",wpan); add("South",pan); add("East",lipslider); add("West",glotslider); add("Center",tubecan); setBackground(Color.lightGray); } public void pcm2ulaw(double src[],byte dest[],int l) // Routine for converting PCM into u-Law // Written 1998 by Joel Niederhauser // // scr: source PCM array Values between -1..1 // dest: destination u-Law array Values between -128..127 // l: length of array { /* double value; // Temporal Value double factor=1.0/Math.log(255.0 +1.0); // Multiplication Factor for speed for (int i=0;i<l;i++) { // Calculation of u-Law value value=factor*Math.log(1.0+255.0*Math.abs(src[i])); if (src[i]<0.0) //signum { dest[i]=(byte) (128.0-12.0*value); // Assign calculated value to destination } else { dest[i]=(byte) (-128.0+12.0*value); // Assign calculated value to destination } }*/ // unfortunately this routine didn't quite work because of the signed byte format! for (int i=0;i<l;i++) { dest[i]=Mulaw.linear2ulaw((short) (src[i]*32760)); } } public void generateglwave(boolean voiced) { //Calulation of glottal pulse if (voiced) { for (int i=0;i<length/4;i++) // Slope { inpwave[i]=-1.0+i*4.0/length; } for (int i=length/4;i<3*length/4;i++) // Half circle { inpwave[i]=4.0*Math.sqrt(length*length/ 16.0-(i-length/2.0)*(i-length/2.0))/length; } for (int i=3*length/4;i<length;i++) // Closed Glottis { inpwave[i]=-1.0; } for (int i=0;i<length-1;i++) // Smooth the waveform { inpwave[i]=(inpwave[i]+inpwave[i+1])*0.95/2.0; } inpwave[length-1]=(inpwave[length-1]+inpwave[0])*0.95/2.0; } else { for (int i=0;i<length-1;i++) inpwave[i]=-1.0+Math.random()*2.0; } } public void calcresponse() // DSP routine to calculate output of lattice Filter { double c=1.0; //constant k=qc double r[]=new double[tlength+1]; //reflections coefficients double li[]=new double[tlength+1]; //to lips input to reflection --(z-1)--li----lo-- double lo[]=new double[tlength+1]; //to lips output of reflection |refl| double gi[]=new double[tlength+1]; //to glottis input to reflection ------go----gi-- double go[]=new double[tlength+1]; //to glottis output of reflection for(int i=0;i<=tlength;i++) //clear all the values { r[i]=0; li[i]=0; lo[i]=0; gi[i]=0; go[i]=0; } r[0]=1.0; //Zgl=0 for (int i=1;i<tlength;i++) { // r=(A2-A1)/(A2+A1); r[i]=(tract[i]*tract[i]-tract[i-1]*tract[i-1])/(tract[i-1]*tract[i-1]+tract[i]*tract[i]); } r[tlength]=1.0;//Zl=Inf. //Main loop for (int l=0;l<10;l++) for (int k=0;k<length;k++) { //Input into system li[0]=inpwave[k]/2.0; //Calculate all reflections for (int i=tlength;i>=0;i--) { //to lips lo[i]=(1+r[i])*li[i]+r[i]*gi[i]; //to glottis go[i]=(1-r[i])*gi[i]-r[i]*li[i]; //To glottis without delay! if(i>1) { gi[i-1]=go[i]; } } //calculate delays towards lips for (int i=0;i<tlength;i++) { li[i+1]=lo[i]; } //Lip output outwave[k]=lo[tlength]; } // smooth start and end of array double dif; dif=outwave[length-1]-outwave[0]; outwave[1]+=dif*0.2; outwave[length-2]-=dif*0.2; outwave[0]+=dif*0.4; outwave[length-1]-=dif*0.4; // Normalize to 0.9 amplitude to prevent clipping double max=0; for (int i=0;i<length;i++) { if (Math.abs(outwave[i])>max) max=Math.abs(outwave[i]); } for (int i=0;i<length;i++) { outwave[i]=outwave[i]*0.9/max; } wpan.outw.repaint(); //Redraw Waveform pcm2ulaw(outwave,sample,length);//Move to samplebuffer } public void preset(String p)// Preset Values for certain Vowels { if (p=="AH") {tract[7]=0.56;tract[6]=0.68;tract[5]=0.68;tract[4]=0.48;tract[3]=0.32;tract[2]=0.16;tract[1]=0.36;tract[0]=0.32;} if (p=="EE") {tract[7]=0.44;tract[6]=0.20;tract[5]=0.16;tract[4]=0.36;tract[3]=0.64;tract[2]=0.80;tract[1]=0.72;tract[0]=0.36;} if (p=="EH") {tract[7]=0.44;tract[6]=0.28;tract[5]=0.40;tract[4]=0.56;tract[3]=0.68;tract[2]=0.72;tract[1]=0.52;tract[0]=0.28;} if (p=="OH") {tract[7]=0.16;tract[6]=0.72;tract[5]=0.48;tract[4]=0.40;tract[3]=0.24;tract[2]=0.20;tract[1]=0.40;tract[0]=0.28;} if (p=="OO") {tract[7]=0.12;tract[6]=0.68;tract[5]=0.48;tract[4]=0.32;tract[3]=0.28;tract[2]=0.32;tract[1]=0.60;tract[0]=0.32;} tubecan.repaint(); calcresponse(); } public void start() // Start Applet { if (sampleStream!=null&&!playing) { AudioPlayer.player.start(sampleStream); playing=true; } generateglwave(true); preset("AH"); } public void stop() // Stop Applet { if (sampleStream!=null) { AudioPlayer.player.stop(sampleStream); playing=false; } } public void actionPerformed(ActionEvent e) // Handle Buttons { String command = e.getActionCommand(); if (command=="start") { if (sampleStream!=null&&!playing) { AudioPlayer.player.start(sampleStream); playing=true; calcresponse(); } } else if (command=="stop") { if (sampleStream!=null) { AudioPlayer.player.stop(sampleStream); playing=false; } } else if (command=="Hello") { generateglwave(false); wpan.inw.repaint(); preset("EH"); try{Thread.sleep(500);}catch (InterruptedException ex) {} generateglwave(true); wpan.inw.repaint(); preset("EH"); try{Thread.sleep(500);}catch (InterruptedException ex) {} preset("OH"); } else { preset(command); } } public Insets insets() { return new Insets(5,5,5,5); } } // end of vocsynth.java