I wrote some code using ChucK : Strongly-timed, Concurrent, and On-the-fly Music Programming Language and would like a review. I broke down the logic into a number of classes, all of which are shown below. This is pretty new to me, and I would appreciate any and all constructive criticism. What the main script fizzbuzz_run.ck does, besides the regular FizzBuzz modulus operations, is play different chords depending on the FizzBuzz result, and throughout print the results and primary oscillator frequency.ClarificationI want to clarify a few things that are specific to ChucK:printing statements to console: <<< print this >>>;The ChucK operator => is a bit like your usual assignment, except reverse order. For example 42 => int theMeaningOfLifeAndEverything; is how you would otherwise int theMeaningOfLifeAndEverything = 42;.Time and duration are handled by the ChucK VM. Code on GithubI uploaded a video to YouTube showing what it sounds like. (video not embedded, link opens YouTube)OscPitch.ckpublic class OscPitch {    this.freq => float freq;    getSemitoneRatio() => float SEMITONE;    // Change the pitch by N musical steps    // @param float steps : the number of steps to change the frequency, positive or negative    // @return float : the changed frequency    fun float change(float steps) {          if (Math.isnan(steps)) {            <<< now, : , steps,  is not a number >>>;            return freq;        } else {            // The semitone ratio has to be applied once for each step change            // hence the use of Math.pow which does everything            // in one operation, rather than having to use a loop            return freq * Math.pow(SEMITONE, steps) => freq;        }    }    // Get the ratio to apply to a frequency to go up or down one semitone    fun float getSemitoneRatio() {        // The ratio for a musical semitone is        // the 12th root of 2, or approximately 1.05946309436        // More info: https://en.wikipedia.org/wiki/Twelfth_root_of_two        return Math.pow(2.0, 1.0/12.0);    }    // Getters and setters    fun float getFreq() { return freq; }    fun void setFreq(float val) { val => freq; }}OscChords.ck// Class used to create chords.public class OscChords {    OscPitch oscPitch;    this.root => float root;    0.05 => float gain;    0.0 => float mute;    SawOsc I, III, V, VII;    string currentChord;    // initialize oscillators    0 => I.freq;    0 => III.freq;    0 => V.freq;    0 => VII.freq;    mute => I.gain;    mute => III.gain;    mute => V.gain;    mute => VII.gain;    I   => dac;    III => dac;    V   => dac;    VII => dac;     // Sets all chord frequencies to 0, making them inaudible.    fun void noChord() {        0 => I.freq;        0 => III.freq;        0 => V.freq;        0 => VII.freq;        no chord => currentChord;    }    // Creates a major chord.    // @input float root : the root note of the chord.    fun void major(float root) {        root => oscPitch.freq => I.freq;        oscPitch.change(4)    => III.freq;        oscPitch.change(3)    => V.freq;        0                     => VII.freq;        major => currentChord;    }    // Creates a minor chord.    // @input float root : the root note of the chord.    fun void minor(float root) {        root => oscPitch.freq => I.freq;        oscPitch.change(3)    => III.freq;        oscPitch.change(4)    => V.freq;        0                     => VII.freq;        minor => currentChord;    }    // Creates a major7 chord.    // @input float root : the root note of the chord.    fun void major7(float root) {        root => oscPitch.freq => I.freq;        oscPitch.change(4)    => III.freq;        oscPitch.change(3)    => V.freq;        oscPitch.change(4)    => VII.freq;        major7 => currentChord;    }    // Sets the gain on the chord to make it audible.    fun void play() {        gain => I.gain;        gain => III.gain;        gain => V.gain;        gain => VII.gain;    }    // Sets the gain on the chord to make it inaudible.    fun void stop() {                mute => I.gain;        mute => III.gain;        mute => V.gain;        mute => VII.gain;    }    // @return string : the name of the current chord.    fun string getCurrentChord() { return currentChord; }}TempoMeter.ckpublic class TempoMeter {    this.bpm => float bpm;    this.upper => int upper;    this.lower => int lower;    60 => int SECONDS_PER_MINUTE;    // default values    1::second => dur beat;    4::beat => dur measure;    // Divide a minute into the number of beats per minute (bpm)    //   and return the duration of 1 beat. Default 60 bpm unless set otherwise.    // @param float bpm : the beats per minute of the tempo    // @return dur : the duration of a beat    fun dur setTempo(float bpm) {        if (Math.isnan(bpm)) {            <<< now, : , bpm,  is not a number >>>;            return beat;        } else {            (SECONDS_PER_MINUTE / bpm)::second => beat;            return beat;        }    }    // Set the meter (or time signature) using traditional Western    //   music notation. Default 4/4 unless set otherwise.    // @param int upper : the number of beats per measure    // @param int lower : the duration ratio (or unit) of a beat    // @return dur : the duration of a measure    fun dur setMeter(int upper, int lower) {        if (Math.isnan(upper) || Math.isnan(lower)) {            <<< now, : , upper,  or , lower,  is not a number >>>;            return measure;        } else {            lower / 4 => float beatDurationRatio;            beat * beatDurationRatio => beat;            upper::beat => measure;            return measure;        }    }    // Getters and setters    fun float getBpm() { return bpm; }    fun void setBpm(float val) { val => bpm; }    fun int getUpper() { return upper; }    fun void setUpper(int val) { val => upper; }    fun int getLower() { return lower; }    fun void setLower(int val) { val => lower; }}fizzbuzz_run.ckTempoMeter tempoMeter;tempoMeter.setTempo(120) => dur beat;tempoMeter.setMeter(4,4) => dur measure;55 => float NOTE_A1;OscPitch oscPitch;NOTE_A1 => oscPitch.freq;1 => int NoteAOctave;OscChords chord;// oscillator to digital audio controllerTriOsc fizzbuzzOsc => dac;oscPitch.freq => chord.root => fizzbuzzOsc.freq;// audio controls0.1 => fizzbuzzOsc.gain;// variables for loopingnow + 12::measure => time stop;1 => int stepCounter;while (now < stop) {    chord.noChord();    <<< ----- >>>;    if (stepCounter % 15 == 0) {        chord.major7(oscPitch.getFreq());        chord.play();        <<< Result: , FizzBuzz, | chord:, chord.getCurrentChord() >>>;    } else if (stepCounter % 5 == 0) {        chord.minor(oscPitch.getFreq());        chord.play();        <<< Result: , Fizz, | chord:, chord.getCurrentChord() >>>;    } else if (stepCounter % 3 == 0) {        chord.major(oscPitch.getFreq());        chord.play();        <<< Result: , Buzz, | chord:, chord.getCurrentChord() >>>;    } else {        <<< Result: , stepCounter >>>;    }    // identify reference A notes and their octave and print them    if (Math.floor(oscPitch.getFreq() % NOTE_A1) == 0.0) {        <<< Reference A, NoteAOctave >>>;        1 +=> NoteAOctave;    }    <<< Osc Freq: , oscPitch.getFreq(), Hz >>>;    beat +=> now;    chord.stop();    oscPitch.change(1) => fizzbuzzOsc.freq;    1 +=> stepCounter;}<<< end program >>>;And just for reference, though this only pulls the classes together at runtime:fizzbuzz.ck// fizzbuzz.ck// This file defines which files to add to the VMMachine.add(OscUtils/OscPitch.ck) => int OscPitchId;Machine.add(OscUtils/OscChords.ck) => int OscChordsId;Machine.add(TimeUtils/TempoMeter.ck) => int TempoMeterId;if(me.args() > 0) {    Machine.add(fizzbuzz_run: + me.arg(0) + .ck);} else {    Machine.add(fizzbuzz_run.ck);}
Musical Fizzbuzz
fizzbuzz;audio;music;chuck
Really interesting thing you've created! I've never used ChucK, but I'll try to comment on some things. I also have some, but limited music theory knowledge. If language constraints is a reason for doing something I've mentioned below, please excuse my ignorance :)Semitone ratio?I wonder why you chose to create the getSemitoneRatio function. It's only used to initialize the SEMITONE variable. Why not just remove that function and put the body as the initializer expression instead?Additionally, I wouldn't actually call it a ratio. It's a factor.// The factor to apply to a frequency to go up or down one semitone// More info: https://en.wikipedia.org/wiki/Twelfth_root_of_twoMath.pow(2.0, 1.0/12.0) => float SEMITONE_FACTOR;Dry initialization// initialize oscillators0 => I.freq;0 => III.freq;0 => V.freq;0 => VII.freq;mute => I.gain;mute => III.gain;mute => V.gain;mute => VII.gain;I   => dac;III => dac;V   => dac;VII => dac; // Sets all chord frequencies to 0, making them inaudible.fun void noChord() {    0 => I.freq;    0 => III.freq;    0 => V.freq;    0 => VII.freq;    no chord => currentChord;}Since you already have the noChord function you can easily remove part of this code duplication. Just call noChord instead of the first four lines after the comment. The side-effect of initializing currentChord as well is probably just good.Dry initialization 2Another approach to this is creating a factory function for SawOsc. Compare the following examples.SawOsc I, III, V, VII;0 => I.freq;0 => III.freq;0 => V.freq;0 => VII.freq;mute => I.gain;mute => III.gain;mute => V.gain;mute => VII.gain;createSawOsc(0, mute) => SawOsc I;createSawOsc(0, mute) => SawOsc III;createSawOsc(0, mute) => SawOsc V;createSawOsc(0, mute) => SawOsc VII;Gain?In OscChord there are two variables representing different gain levels.0.05 => float gain;0.0 => float mute;It's confusing that one is just named gain while the other one is called mute. Perhaps call them muteGain and playGain to show their relationship.