//------------------------------------------------------------------------------ // name: poem-ungenerate.ck // desc: they just keep coming; more sus poem word2vec examples // // version: need chuck version 1.5.0.0 or higher // sorting: part of ChAI (ChucK for AI) // // NOTE: because this example needs ConsoleInput, it needs to // run command line chuck // > chuck poem-ungenerate.ck // // "Degenerate" // -- a stream of unconsciousness poem generator // // this example prompts for a line of text // (e.g., roses are red) and a number of iterations, // and outputs a series of lines that use word2vec to // change one word to an adjacent word from the previous // iteration, while generating unsettling descending // shepard tones // // NOTE: need a pre-trained word vector model, e.g., from // https://chuck.stanford.edu/chai/data/glove/ // glove-wiki-gigaword-50-tsne-2.txt (400000 words x 2 dimensions) // // author: Ge Wang // date: Spring 2023 //------------------------------------------------------------------------------ // random seed (if set, sequence can be reproduce) // Math.srandom( 515 ); // instantiate Word2Vec model; // pre-trained model to load me.dir() + "glove-wiki-gigaword-50-tsne-2.txt" => string filepath; // load pre-trained model (see URLs above for download) if( !model.load( filepath ) ) { <<< "cannot load model:", filepath >>>; me.exit(); } // conditions 3 => int LINES_PER_STANZA; 3 => int NUM_SECTIONS; // here each section is a fixed stanzas format // number of nearest words to retrieve for each word // higher this number the higher the variance per word 10 => int K_NEAREST; // timing 100::ms => dur T_WORD; // duration per word false => int shouldScaleTimeToWordLength; // longer words take longer? T_WORD => dur T_LINE_PAUSE; // a little pause after each line T_WORD * 2 => dur T_STANZA_PAUSE; // pause after each stanza // make a ConsoleInput ConsoleInput in; // tokenizer StringTokenizer tok; // line string line[0]; // ------------ audio: shepard tone generator --------- // starting pitches (in MIDI note numbers, octaves apart) [ 12.0, 24, 36, 48, 60, 72, 84, 96, 108 ] @=> float pitches[]; // number of tones pitches.size() => int N; // bank of tones TriOsc tones[N]; // overall gain Gain gain => Envelope envelope => NRev reverb => dac; 1.0/N => gain.gain; .05 => reverb.mix; // connect to dac for( int i; i < N; i++ ) { tones[i] => gain; } // set envelope envelope.duration(1::second); // increment per unit time (use negative for descending) -.001 => float INC; // keep shepard tone generating in the background; // we will raise and lower volume spork ~ shepard(); // ------------ audio: shepard tone generator --------- // current word string word; // word vector float vec[model.dim()]; // search results string words[K_NEAREST]; // state 0 => int GET_TEXT; 1 => int GET_NUM; GET_TEXT => int state; // prompt string prompt; // the line string theText[0]; // new line endl(); // print program name chout <= "\"Degenerate\"" <= IO.newline(); chout <= "-- a stream of unconsciousness poem generator" <= IO.newline(); chout.flush(); // new line endl(); // loop while( true ) { if( state == GET_TEXT ) { "enter a line (e.g., roses are red) => " => prompt; } else { "how many degenerations (e.g., 20) => " => prompt; } // prompt in.prompt( prompt ) => now; // read while( in.more() ) { // clear tokens array line.clear(); // clear if in text input mode if( state == GET_TEXT ) theText.clear(); // get the input line as one string; send to tokenizer tok.set( in.getLine() ); // tokenize line into words separated by white space while( tok.more() ) { // put into array (lowercased) line << tok.next().lower(); // copy into our text for later if( state == GET_TEXT ) theText << line[line.size()-1]; } // if non-empty if( !line.size() ) continue; // check state if( state == GET_TEXT ) { // set state to GET_NUM GET_NUM => state; } else // GET_NUM { // check input Std.atoi( line[0] ) => int n; // make sure in range if( n <= 0 ) continue; // degenerate the input across n iterations degenerate( theText, n ); // reset state to get text GET_TEXT => state; } } } // do something with the text input fun void degenerate( string input[], int iterations ) { // start envelope (bring up audio) envelope.keyOn(); // endline endl(); // each iteration updates one word from the previous for( int i; i < iterations; i++ ) { for( int j; j < input.size(); j++ ) { say(input[j]); wait(); } endl(); // random word in the line Math.random2(0,input.size()-1) => int which; // take that work input[which] => word; // find similar words model.getSimilar( word, words.size(), words ); // randomly take one and replace the word in question words[Math.random2(0,words.size()-1)] => input[which]; } // close envelope (ramp down audio) envelope.keyOff(); // endline endl(); // pause T_STANZA_PAUSE => now; } // say a word with space after fun void say( string word ) { say( word, " " ); } // say a word fun void say( string word, string append ) { // print it chout <= word <= append; chout.flush(); } // wait fun void wait() { wait( T_WORD ); } // wait fun void wait( dur T ) { // let time pass, let sound...sound T => now; } // new line with timing fun void endl() { endl( T_WORD ); } // new line with timing fun void endl( dur T ) { // new line chout <= IO.newline(); chout.flush(); // let time pass T => now; } // shepard tone generator fun void shepard() { //-------------------------------------------------------------------- // continuous shepard-risset tone generator // adapted from https://chuck.stanford.edu/doc/examples/deep/shepard.ck //-------------------------------------------------------------------- // mean for normal intensity curve 66 => float MU; // standard deviation for normal intensity curve 42 => float SIGMA; // normalize to 1.0 at x==MU 1 / Math.gauss(MU, MU, SIGMA) => float SCALE; // unit time (change interval) 1::ms => dur T; // infinite time loop while( true ) { for( int i; i < N; i++ ) { // set frequency from pitch pitches[i] => Std.mtof => tones[i].freq; // compute loundess for each tone Math.gauss( pitches[i], MU, SIGMA ) * SCALE => float intensity; // map intensity to amplitude intensity*96 => Math.dbtorms => tones[i].gain; // increment pitch INC +=> pitches[i]; // wrap (for positive INC) if( pitches[i] > 120 ) 108 -=> pitches[i]; // wrap (for negative INC) else if( pitches[i] < 12 ) 108 +=> pitches[i]; } // advance time T => now; } }