Wednesday 6 March 2019

Random syllable generator for conlanging

One of the most tiring things about conlanging is generating new words. You could always come up with random words, which works quite well when the number of words you need to generate is in the tens or maybe in the early triple digits. However, there comes a point where you need to formalise the process. Here, I will explain in detail how it can be done, and give  a sample c++ program that can be used for the purpose.

I have described some of the basics in my post Conlanging adventures 001. I will reiterate some of the points here, to make it easier to follow.

So, to generate vocabulary for a conlang, you need:
  1. A sound system: What are the sounds that exist in your language? What consonants exist? What vowels exist? I would recommend using an IPA chart to decide on the sounds that you want to use. You can decide on the exact number of sounds you want to use - after all, some languages have more sounds in them than the others. You may also want to decide on whether you want tones in your language.
  2. How the sounds are combined: In any language, there are specific ways in which sounds can be combines. In English, for example, you can end a syllable in 'ng', but you can't start a syllable with it. This means you have to decide on the possible starting consonant clusters, vowels, and ending consonant clusters. You can choose to eliminate one of the consonant clusters completely (i.e., if it's a language like Japanese, the only possible ending consonants are 'n' or nothing, with nothing being much more common). If you're particularly daring, or if you're Russian, you can go ahead and make the vowel cluster optional as well. You have to decide the maximum length for each cluster as well.
If you have decided on these, we can go on to generate syllables.

Let's start with a simple language as a sample. First, let's decide on a set of simple sounds.
Consonants: k, g, p, t, d, n, m, r, h, s
Vowels: a, e, i, o, u

Next, we need to decide on possible consonant clusters.
Starting consonants: k, kr, g, gr, p, t, tr, d, n, m, r, h, s, sr
Vowels: a, e, i, o, u, ai (basic vowels + one diphthong)
Ending consonants: k, kt, g, p, pt, t, d, n, ng, m, r, s

I will consider the starting and ending consonant clusters optional, so the generator would have the option to drop them.

Without further ado, let's get to the program.

EDIT: there is a problem that I discovered after testing the program - cin will read all the characters you input, and use it as the input the next time cin is called (for example, if you type "yes" instead of "y", it will read that input as "y", the next input as "e", and the next as "s"). The fix is to clear cin and skip over all the other characters in the buffer. The commands used for the purpose are cin.clear() and cin.ignore(10000, '\n');. I have highlighted those edits in red in the code.


#include <cstdlib>
#include <iostream>
#include <string>
#include <random>
#include <time.h>


using namespace std;

#define LENGTH_STCONST 15
#define LENGTH_VOWELS 5
#define LENGTH_TECONST 13


string stConst[LENGTH_STCONST] = {"k","kr","g","gr","p","t","tr","d","m","n","r","h","s","sr"};

string vowels[LENGTH_VOWELS] = {"a","e","i","o","u"};

string teConst[LENGTH_TECONST] = {"k","kt","g","p","pt","t","d","n","ng","m","r","s"};


/*
 *
 */
int main(int argc, char** argv) {
   
    //Loop exit variables
    int end = 0; //main loop
    int st_end = 0; int v_end = 0; int te_end = 0;//cluster loops
   
    //For user input
    char inChar = '\0';
    //For addressing arrays
    int index_0,index_1,index_2;
   
    //random number generator
    default_random_engine generator;
    //distributions - three uniform int distributions used.
    uniform_int_distribution<int> distribution_0(0,(LENGTH_STCONST-1));
    uniform_int_distribution<int> distribution_1(0,(LENGTH_VOWELS-1));
    uniform_int_distribution<int> distribution_2(0,(LENGTH_TECONST-1));
   
    /*discard random number of random numbers*/
    time_t now = time(0);
    cout<<now<<endl;
    int skip_time = now%10000;
    cout<<"skip time: "<<skip_time<<endl;
    for (int i = 0; i<skip_time; i++){
        index_0 = distribution_0(generator);
        index_1 = distribution_1(generator);
        index_2 = distribution_2(generator);
    }
   
    //main loop
    while (end==0){
        //set cluster loop variables to zero
        st_end = 0; v_end = 0; te_end = 0;
       
        //User input
        cout<<"Generate new syllable? Y/N"<<endl;
        cin>>inChar;

        cin.clear();
        cin.ignore(10000, '\n');
       
        //Exit program if exit command is given
        if ((inChar=='n')||(inChar=='N')){
            cout<<"Exiting loop"<<endl;
            end = 1;    //set end variable
            continue;   //exit loop
        }
       
        //generate index using random number generator
        index_0 = distribution_0(generator);
        index_1 = distribution_1(generator);
        index_2 = distribution_2(generator);
        cout<<index_0<<" "<<index_1<<" "<<index_2<<endl;
       
        //print generated syllable
        cout<<stConst[index_0]<<" "<<vowels[index_1]<<" "<<teConst[index_2]<<endl;
       
        //cluster loops, to modify either starting, vowel, or end clusters only
        while(st_end == 0){
            cout<<"Generate new starting consonant cluster? Y/N"<<endl;
            cin>>inChar;

            cin.clear();
            cin.ignore(10000, '\n')
            if ((inChar=='n')||(inChar=='N')){
                st_end=1;
                continue;
            }
            else{
                index_0 = distribution_0(generator);
                cout<<stConst[index_0]<<" "<<vowels[index_1]<<" "<<teConst[index_2]<<endl;
            }
        }
       
        while(v_end == 0){
            cout<<"Generate new vowel cluster? Y/N"<<endl;
            cin>>inChar;

            cin.clear();
            cin.ignore(10000, '\n')
            if ((inChar=='n')||(inChar=='N')){
                v_end = 1;
                continue;
            }
            else{
                index_1 = distribution_1(generator);
                cout<<stConst[index_0]<<" "<<vowels[index_1]<<" "<<teConst[index_2]<<endl;
            }
        }
       
        while (te_end == 0){
            cout<<"Generate new ending consonant cluster? Y/N"<<endl;
            cin>>inChar;

            cin.clear();
            cin.ignore(10000, '\n')
            if ((inChar=='n')||(inChar=='N')){
                te_end = 1;
                continue;
            }
            else{
                index_2 = distribution_2(generator);
                cout<<stConst[index_0]<<" "<<vowels[index_1]<<" "<<teConst[index_2]<<endl;
            }
        }
    }

    return 0;
}



Explanation:
Includes: iostream - used for cin and cout.
string - we need strings, because we have multiple letters in a cluster. Alternatively, you can use 2D arrays (which you will have to use if you use C instead of C++), but I find strings more versatile.
random - used to generate random numbers.
time.h - used to randomize random numbers using current time, because I find that it keeps generating the same syllables otherwise.

Defines: used to define the size of each of the string arrays that follow. If you need to make a cluster optional, I would recommend making the size of the array larger than the number of consonants you have.

Array declerations:  
string stConst[LENGTH_STCONST] = {"k","kr","g","gr","p","t","tr","d","m","n","r","h","s","sr"};
Here, an array of strings is used to store all possible starting consonant combinations. As you might have noticed, the length of the array is defined as 15, though there are 14 entries. The extra entry remains null, which is used to account for the fact that the starting consonant cluster is optional.
The vowel clusters and terminal consonant clusters are defined the same way. The terminal consonant cluster also has an extra, null entry.

Main program:
First, the exit variables for the loops are declared. Then, the input character for user input, and variables for array indexes are created. Afterwards, the random number generator is declared. You could use rand() instead, but this method is a little more reliable.
 Next, a random number of the generated numbers are discarded. This is because the program tends to generate the same random numbers, and thus the same syllables otherwise. To do this, time_t now = time(0) is used to get the current time in seconds from 00:00, 1st January, 1970. Since I have no intention of looping that many times, I take the modulus of 10,000, and discard that many random numbers from each distribution.

Main while loop:
Here, first, the cluster loop end markers are reset to zero. Then the user is prompted to input whether they want a syllable generated. Then, the random number generators defined are used to generated three random integers in the range  (0, length of array) for each of the three components. This is then displayed.

Then comes the optional part. The three cluster modification loops allow the user to regenerate one of the three components while keeping the other two components of the syllable the same. I find it useful for the conlang I use regularly (because there are so many sounds, some technically possible combinations sound weird) but you can delete the three cluster loops if you want to.

Program output: 
The program output is as follows. For clarity, I will highlight user inputs in red and generated syllables in green.

1551861330
skip time: 1330
Generate new syllable? Y/N
y
13 3 4
sr o pt
Generate new starting consonant cluster? Y/N
y
n o pt
Generate new starting consonant cluster? Y/N
y
gr o pt
Generate new starting consonant cluster? Y/N
y
 o pt
Generate new starting consonant cluster? Y/N
y
h o pt
Generate new starting consonant cluster? Y/N
n
Generate new vowel cluster? Y/N
n
Generate new ending consonant cluster? Y/N
y
h o p
Generate new ending consonant cluster? Y/N
y
h o m
Generate new ending consonant cluster? Y/N
y
h o kt
Generate new ending consonant cluster? Y/N
y
h o t
Generate new ending consonant cluster? Y/N
y
h o n
Generate new ending consonant cluster? Y/N
y
h o pt
Generate new ending consonant cluster? Y/N
n
Generate new syllable? Y/N
y
4 0 1
p a kt
Generate new starting consonant cluster? Y/N
n
Generate new vowel cluster? Y/N
n
Generate new ending consonant cluster? Y/N
n
Generate new syllable? Y/N
y
0 2 4
k i pt
Generate new starting consonant cluster? Y/N
n
Generate new vowel cluster? Y/N
n
Generate new ending consonant cluster? Y/N
y
k i g
Generate new ending consonant cluster? Y/N
y
k i kt
Generate new ending consonant cluster? Y/N
y
k i t
Generate new ending consonant cluster? Y/N
n
Generate new syllable? Y/N
n
Exiting loop

RUN SUCCESSFUL (total time: 55s)


I hope you found this useful. If there is anything you would like clarified, please mention it the comments below and I will reply as soon as possible.

You can follow me on Facebook here for news and updates.

Until next time!




No comments:

Post a Comment

How to write a character who is smarter than you

We all have that one character (or few) who is significantly smarter than the writer. So, as a writer, how do you write such a character con...