# -*- coding: utf-8 -*-
import os
import time

import myro.osc as osc

# ChucK unit generator documentation at
# http://chuck.cs.princeton.edu/doc/program/ugen_full.html

# initialize osc once


def initChucK():
    # init osc
    osc.init()
    codeDir, filename = osc.__file__.rsplit(os.sep, 1)
    os.chdir(codeDir)
    # "C:\\Python24\\Lib\\site-packages\\myro\\file.py"
    # start chuck
    if os.name in ["nt", "dos", "os2"]:
        os.system("start chuck oscrecv")
    elif os.name in ["posix"]:
        os.system("chuck oscrecv &")
    else:
        raise AttributeError(
            "your operating system (%s) is not currently supported" % os.name
        )


def initChuck():
    initChucK()


# base instrument class (not to be instantiated)
class Instrument:
    name = "none"

    # connect to output
    def connect(self):
        osc.sendMsg("/inst/" + self.name + "/connect", ["dac", 1])

    # disconnect from output
    def disconnect(self):
        osc.sendMsg("/inst/" + self.name + "/connect", ["dac", 0])

    # change gain [0.0-1.0]
    def setGain(self, gain):
        if gain >= 0:  # and gain <= 1:
            osc.sendMsg("/inst/" + self.name + "/gain", [gain * 1.0])

    # change frequency
    def setFrequency(self, freq):
        osc.sendMsg("/inst/" + self.name + "/freq", [freq * 1.0])

    # "note on"
    def noteOn(self, velocity):
        if self.name == "sinosc" or self.name == "sndbuf":
            osc.sendMsg("/inst/" + self.name + "/gain", [velocity * 1.0])
        else:
            osc.sendMsg("/inst/" + self.name + "/noteOn", [velocity * 1.0])

    # "note off"
    def noteOff(self, velocity):
        if self.name == "sinosc" or self.name == "sndbuf":
            osc.sendMsg("/inst/" + self.name + "/gain", [0.0])
        else:
            osc.sendMsg("/inst/" + self.name + "/noteOff", [velocity * 1.0])


# derived classes for specific instruments

# Sinusoidal oscillator (SinOsc)
class SineWave(Instrument):
    # initialize name, send message to ChucK to load the functions
    # for this particular instrument, time.sleep a bit for it to do so.
    def __init__(self):
        self.name = "sinosc"
        osc.sendMsg("/init", [self.name])
        time.sleep(0.5)


# Mandolin
class Mandolin(Instrument):
    # initialize name, send message to ChucK to load the functions
    # for this particular instrument, time.sleep a bit for it to do so.
    def __init__(self):
        self.name = "mandolin"
        osc.sendMsg("/init", [self.name])
        time.sleep(0.5)

    # pluck the mandolin [0.0-1.0]
    def pluck(self, strength):
        if strength >= 0 and strength <= 1:
            osc.sendMsg("/inst/mandolin/pluck", [strength * 1.0])


# FM Voices
class FMVoices(Instrument):
    # initialize name, send message to ChucK to load the functions
    # for this particular instrument, time.sleep a bit for it to do so.
    def __init__(self):
        self.name = "fmvoices"
        osc.sendMsg("/init", [self.name])
        time.sleep(0.5)

    # set vowel [0.0-1.0]
    def setVowel(self, vowel):
        if vowel >= 0 and vowel <= 1:
            osc.sendMsg("/inst/fmvoices/vowel", [vowel * 1.0])

    # set spectral tilt [0.0-1.0] (related to loudness?)
    def setSpectralTilt(self, spectralTilt):
        if spectralTilt >= 0 and spectralTilt <= 1:
            osc.sendMsg("/inst/fmvoices/spectralTilt", [spectralTilt * 1.0])

    # set ADSR targets [0.0-1.0]
    def setAdsrTarget(self, target):
        if target >= 0 and target <= 1:
            osc.sendMsg("/inst/fmvoices/adsrTarget", [target * 1.0])


# More singing synthesis (VoicForm)
class Voice(Instrument):
    # initialize name, send message to ChucK to load the functions
    # for this particular instrument, time.sleep a bit for it to do so.
    def __init__(self):
        self.name = "voicform"
        osc.sendMsg("/init", [self.name])
        time.sleep(0.5)

    # select phoneme via one of these strings
    # "eee"  "ihh"  "ehh"  "aaa"
    # "ahh"  "aww"  "ohh"  "uhh"
    # "uuu"  "ooo"  "rrr"  "lll"
    # "mmm"  "nnn"  "nng"  "ngg"
    # "fff"  "sss"  "thh"  "shh"
    # "xxx"  "hee"  "hoo"  "hah"
    # "bbb"  "ddd"  "jjj"  "ggg"
    # "vvv"  "zzz"  "thz"  "zhh"
    def setPhoneme(self, phoneme):
        osc.sendMsg("/inst/voicform/phoneme", [phoneme])

    # select phoneme via number [0-128]
    def setPhonemeNumber(self, number):
        if number >= 0 and number <= 128:
            osc.sendMsg("/inst/voicform/phonemeNum", [int(number)])

    # start singing [0.0-1.0]
    def sing(self, floatValue):
        if floatValue < 0 or floatValue > 1:
            print("VoicForm sing: floatValue should be between 0.0 and 1.0")
        else:
            osc.sendMsg("/inst/voicform/speak", [floatValue * 1.0])

    # stop singing [0.0-1.0]
    def quiet(self, floatValue):
        if floatValue >= 0 and floatValue <= 1:
            osc.sendMsg("/inst/voicform/quiet", [floatValue * 1.0])

    # set mix for voiced component [0.0-1.0]
    def setVoiced(self, mix):
        if mix >= 0 and mix <= 1:
            osc.sendMsg("/inst/voicform/voiced", [mix * 1.0])

    # set mix for unvoiced component [0.0-1.0]
    def setUnvoiced(self, mix):
        if mix >= 0 and mix <= 1:
            osc.sendMsg("/inst/voicform/unVoiced", [mix * 1.0])

    # set voiced/unvoiced mix [0.0-1.0]
    def setVoiceMix(self, mix):
        if mix >= 0 and mix <= 1:
            osc.sendMsg("/inst/voicform/voiceMix", [mix * 1.0])

    # pitch sweep [0.0-1.0]
    def setPitchSweepRate(self, rate):
        if rate >= 0 and rate <= 1:
            osc.sendMsg("/inst/voicform/pitchSweepRate", [rate * 1.0])

    # set vibrato frequency [Hz] and gain [0.0-1.0]
    def setVibrato(self, freq, gain):
        if gain >= 0 and gain <= 1:
            osc.sendMsg("/inst/voicform/vibratoFG", [freq * 1.0, gain * 1.0])

    # set loudness [0.0-1.0]
    def setLoudness(self, loudness):
        if loudness >= 0 and loudness <= 1:
            osc.sendMsg("/inst/voicform/loudness", [loudness * 1.0])


# Karplus Strong plucked string (StifKarp)
class PluckedString(Instrument):
    # initialize name, send message to ChucK to load the functions
    # for this particular instrument, time.sleep a bit for it to do so.
    def __init__(self):
        self.name = "stifkarp"
        osc.sendMsg("/init", [self.name])
        time.sleep(0.5)

    # set pickup position [0.0-1.0]
    def setPickupPosition(self, position):
        if position >= 0 and position <= 1:
            osc.sendMsg("/inst/stifkarp/pickupPosition", [position * 1.0])

    # set string sustain [0.0-1.0]
    def setSustain(self, sustain):
        if sustain >= 0 and sustain <= 1:
            osc.sendMsg("/inst/stifkarp/sustain", [sustain * 1.0])

    # set string stretch [0.0-1.0]
    def setStretch(self, stretch):
        if stretch >= 0 and stretch <= 1:
            osc.sendMsg("/inst/stifkarp/stretch", [stretch * 1.0])

    # pluck the string [0.0-1.0]
    def pluck(self, strength):
        if strength >= 0 and strength <= 1:
            osc.sendMsg("/inst/stifkarp/pluck", [strength * 1.0])

    # set base loop gain [0.0-1.0]
    def setBaseLoopGain(self, gain):
        if gain >= 0 and gain <= 1:
            osc.sendMsg("/inst/stifkarp/baseLoopGain", [gain * 1.0])


# Sitar
class Sitar(Instrument):
    # initialize name, send message to ChucK to load the functions
    # for this particular instrument, time.sleep a bit for it to do so.
    def __init__(self):
        self.name = "sitar"
        osc.sendMsg("/init", [self.name])
        time.sleep(0.5)

    # pluck the string [0.0-1.0]
    def pluck(self, strength):
        if strength >= 0 and strength <= 1:
            osc.sendMsg("/inst/sitar/pluck", [strength * 1.0])


# Shakers (collisions of multiple independent sound-producing objects)
class Shakers(Instrument):
    # initialize name, send message to ChucK to load the functions
    # for this particular instrument, time.sleep a bit for it to do so.
    def __init__(self):
        self.name = "shakers"
        osc.sendMsg("/init", [self.name])
        time.sleep(0.5)

    # select instrument by index as follows:
    #    -Maraca = 0
    #    - Cabasa = 1
    #    - Sekere = 2
    #    - Guiro = 3
    #    - Water Drops = 4
    #    - Bamboo Chimes = 5
    #    - Tambourine = 6
    #    - Sleigh Bells = 7
    #    - Sticks = 8
    #    - Crunch = 9
    #    - Wrench = 10
    #    - Sand Paper = 11
    #    - Coke Can = 12
    #    - Next Mug = 13
    #    - Penny + Mug = 14
    #    - Nickle + Mug = 15
    #    - Dime + Mug = 16
    #    - Quarter + Mug = 17
    #    - Franc + Mug = 18
    #    - Peso + Mug = 19
    #    - Big Rocks = 20
    #    - Little Rocks = 21
    #    - Tuned Bamboo Chimes = 22
    def preset(self, number):
        if number >= 0 and number <= 22:
            osc.sendMsg("/inst/shakers/preset", [int(number)])

    # set shake energy [0.0-1.0]
    def setEnergy(self, shakeEnergy):
        if shakeEnergy >= 0 and shakeEnergy <= 1:
            osc.sendMsg("/inst/shakers/energy", [shakeEnergy * 1.0])

    # set system decay [0.0-1.0]
    def setDecay(self, decay):
        if decay >= 0 and decay <= 1:
            osc.sendMsg("/inst/shakers/decay", [decay * 1.0])

    # set number of objects [0.0-128.0]
    def setObjects(self, numObjects):
        if numObjects >= 0 and numObjects <= 128:
            osc.sendMsg("/inst/shakers/objects", [numObjects * 1.0])


# Saxophone / wind instruments (Saxofony)
class Saxophone(Instrument):
    # initialize name, send message to ChucK to load the functions
    # for this particular instrument, time.sleep a bit for it to do so.
    def __init__(self):
        self.name = "saxofony"
        osc.sendMsg("/init", [self.name])
        time.sleep(0.5)

    # set reed stiffness [0.0-1.0]
    def setStiffness(self, stiffness):
        if stiffness >= 0 and stiffness <= 1:
            osc.sendMsg("/inst/saxofony/stiffness", [stiffness * 1.0])

    # set reed aperture [0.0-1.0]
    def setAperture(self, aperture):
        if aperture >= 0 and aperture <= 1:
            osc.sendMsg("/inst/saxofony/aperture", [aperture * 1.0])

    # set pressure / volume [0.0-1.0]
    def setPressure(self, pressure):
        if pressure >= 0 and pressure <= 1:
            osc.sendMsg("/inst/saxofony/pressure", [pressure * 1.0])

    # set vibrato frequency [Hz], vibrato gain [0.0-1.0], and
    # noise component gain [0.0-1.0]
    def setVibrato(self, vibratoFreq, vibratoGain, noiseGain):
        if vibratoGain >= 0 and vibratoGain <= 1 and noiseGain >= 0 and noiseGain <= 1:
            osc.sendMsg(
                "/inst/saxofony/vibrato",
                [vibratoFreq * 1.0, vibratoGain * 1.0, noiseGain * 1.0],
            )

    # set blow position / lip stiffness [0.0-1.0]
    def setBlowPosition(self, position):
        if position >= 0 and position <= 1:
            osc.sendMsg("/inst/saxofony/blowPosition", [position * 1.0])

    # start blowing [0.0-1.0]
    def startBlowing(self, strength):
        if strength >= 0 and strength <= 1:
            osc.sendMsg("/inst/saxofony/startBlowing", [strength * 1.0])

    # stop blowing [0.0-1.0]
    def stopBlowing(self, strength):
        if strength >= 0 and strength <= 1:
            osc.sendMsg("/inst/saxofony/stopBlowing", [strength * 1.0])

    # set rate of attack [seconds]
    def setAttackRate(self, seconds):
        if seconds > 0:
            osc.sendMsg("/inst/saxofony/rate", [seconds * 1.0])


# Moog synthesizer (Moog)
class MoogSynthesizer(Instrument):
    # initialize name, send message to ChucK to load the functions
    # for this particular instrument, time.sleep a bit for it to do so.
    def __init__(self):
        self.name = "moog"
        osc.sendMsg("/init", [self.name])
        time.sleep(0.5)

    # filter Q value [0.0-1.0]
    def setFilterQ(self, floatValue):
        if floatValue >= 0 and floatValue <= 1:
            osc.sendMsg("/inst/moog/filterQ", [floatValue * 1.0])

    # set filter sweep rate [0.0-1.0]
    def setFilterSweepRate(self, rate):
        if rate >= 0 and rate <= 1:
            osc.sendMsg("/inst/moog/filterSweepRate", [rate * 1.0])

    # set vibrato frequency [Hz] and gain [0.0-1.0]
    def setVibrato(self, freq, gain):
        if gain >= 0 and gain <= 1:
            osc.sendMsg("/inst/moog/vibratoFG", [freq * 1.0, gain * 1.0])

    # set aftertouch [0.0-1.0]
    def setAfterTouch(self, afterTouch):
        if afterTouch >= 0 and afterTouch <= 1:
            osc.sendMsg("/inst/moog/afterTouch", [afterTouch * 1.0])


# Struck bar instruments (ModalBar)
class StruckBar(Instrument):
    # initialize name, send message to ChucK to load the functions
    # for this particular instrument, time.sleep a bit for it to do so.
    def __init__(self):
        self.name = "modalbar"
        osc.sendMsg("/init", [self.name])
        time.sleep(0.5)

    # set stick hardness [0.0-1.0]
    def setStickHardness(self, hardness):
        if hardness >= 0 and hardness <= 1:
            osc.sendMsg("/inst/modalbar/stickHardness", [hardness * 1.0])

    # set strike position [0.0-1.0]
    def setStrikePosition(self, position):
        if position >= 0 and position <= 1:
            osc.sendMsg("/inst/modalbar/strikePosition", [position * 1.0])

    # set vibrato frequency [Hz] and gain [0.0-1.0]
    def setVibrato(self, freq, gain):
        if gain >= 0 and gain <= 1:
            osc.sendMsg("/inst/modalbar/vibratoFG", [freq * 1.0, gain * 1.0])

    # set direct gain [0.0-1.0]
    def setDirectGain(self, gain):
        if gain >= 0 and gain <= 1:
            osc.sendMsg("/inst/modalbar/directGain", [gain * 1.0])

    # set master gain [0.0-1.0]
    def setMasterGain(self, gain):
        if gain >= 0 and gain <= 1:
            osc.sendMsg("/inst/modalbar/masterGain", [gain * 1.0])

    # set volume [0.0-1.0]
    def setVolume(self, volume):
        if volume >= 0 and volume <= 1:
            osc.sendMsg("/inst/modalbar/volume", [volume * 1.0])

    # choose preset instrument according to following integer index:
    #     - Marimba = 0
    #     - Vibraphone = 1
    #     - Agogo = 2
    #     - Wood1 = 3
    #     - Reso = 4
    #     - Wood2 = 5
    #     - Beats = 6
    #     - Two Fixed = 7
    #     - Clump = 8
    def preset(self, instrumentNumber):
        if instrumentNumber >= 0 and instrumentNumber <= 8:
            osc.sendMsg("/inst/modalbar/preset", [int(instrumentNumber)])

    # strike bar [0.0-1.0]
    def strike(self, strength):
        if strength >= 0 and strength <= 1:
            osc.sendMsg("/inst/modalbar/strike", [strength * 1.0])

    # damp the bar [0.0-1.0]
    def damp(self, amount):
        if amount >= 0 and amount <= 1:
            osc.sendMsg("/inst/modalbar/damp", [amount * 1.0])

    # set mode info: mode [0 or 1], mode ratio [float], mode radius [0.0-1.0],
    # mode gain [0.0-1.0]
    def setMode(self, mode, ratio, radius, gain):
        if (
            mode >= 0
            and mode <= 1
            and radius >= 0
            and radius <= 1
            and gain >= 0
            and gain <= 1
        ):
            osc.sendMsg(
                "/inst/modalbar/mode",
                [int(mode), ratio * 1.0, radius * 1.0, gain * 1.0],
            )


# Blown bottle (BlowBotl)
class BlowBottle(Instrument):
    # initialize name, send message to ChucK to load the functions
    # for this particular instrument, time.sleep a bit for it to do so.
    def __init__(self):
        self.name = "blowbotl"
        osc.sendMsg("/init", [self.name])
        time.sleep(0.5)

    # set vibrato frequency [Hz], vibrato gain [0.0-1.0] and noise gain [0.0-1.0]
    def setVibrato(self, vibratoFreq, vibratoGain, noiseGain):
        if vibratoGain >= 0 and vibratoGain <= 1 and noiseGain >= 0 and noiseGain <= 1:
            osc.sendMsg(
                "/inst/blowbotl/vibrato",
                [vibratoFreq * 1.0, vibratoGain * 1.0, noiseGain * 1.0],
            )

    # set volume [0.0-1.0]
    def setVolume(self, volume):
        if volume >= 0 and volume <= 1:
            osc.sendMsg("/inst/blowbotl/volume", [volume * 1.0])

    # set rate of attack [seconds]
    def setAttackRate(self, seconds):
        if seconds > 0:
            osc.sendMsg("/inst/blowbotl/rate", [seconds * 1.0])

    # start blowing [0.0-1.0]
    def startBlowing(self, strength):
        if strength >= 0 and strength <= 1:
            osc.sendMsg("/inst/blowbotl/startBlowing", [strength * 1.0])

    # stop blowing [0.0-1.0]
    def stopBlowing(self, floatValue):
        if floatValue >= 0 and floatValue <= 1:
            osc.sendMsg("/inst/blowbotl/stopBlowing", [floatValue * 1.0])


# Clarinet or other blowhole models
class BlowHole(Instrument):
    # initialize name, send message to ChucK to load the functions
    # for this particular instrument, time.sleep a bit for it to do so.
    def __init__(self):
        self.name = "blowhole"
        osc.sendMsg("/init", [self.name])
        time.sleep(0.5)

    # set reed stiffness [0.0-1.0]
    def setReedStiffness(self, stiffness):
        if stiffness >= 0 and stiffness <= 1:
            osc.sendMsg("/inst/blowhole/reed", [stiffness * 1.0])

    # set noise component gain [0.0-1.0]
    def setNoiseGain(self, gain):
        if gain >= 0 and gain <= 1:
            osc.sendMsg("/inst/blowhole/noiseGain", [gain * 1.0])

    # set tonehole size [0.0-1.0]
    def setToneHoleSize(self, size):
        if size >= 0 and size <= 1:
            osc.sendMsg("/inst/blowhole/tonehole", [size * 1.0])

    # set vent frequency [0.0-1.0]
    def setVent(self, vent):
        if vent >= 0 and vent <= 1:
            osc.sendMsg("/inst/blowhole/vent", [vent * 1.0])

    # set pressure [0.0-1.0]
    def setPressure(self, pressure):
        if pressure >= 0 and pressure <= 1:
            osc.sendMsg("/inst/blowhole/pressure", [pressure * 1.0])

    # start blowing [0.0-1.0]
    def startBlowing(self, strength):
        if strength >= 0 and strength <= 1:
            osc.sendMsg("/inst/blowhole/startBlowing", [strength * 1.0])

    # stop blowing [0.0-1.0]
    def stopBlowing(self, floatValue):
        if floatValue >= 0 and floatValue <= 1:
            osc.sendMsg("/inst/blowhole/stopBlowing", [floatValue * 1.0])

    # set rate of attack [seconds]
    def setAttackRate(self, seconds):
        if seconds > 0:
            osc.sendMsg("/inst/blowhole/rate", [seconds * 1.0])


# Bowed string model
class Bowed(Instrument):
    # initialize name, send message to ChucK to load the functions
    # for this particular instrument, time.sleep a bit for it to do so.
    def __init__(self):
        self.name = "bowed"
        osc.sendMsg("/init", [self.name])
        time.sleep(0.5)

    # set bow pressure [0.0-1.0] and position [0.0-1.0]
    def setBow(self, pressure, position):
        if pressure >= 0 and pressure <= 1 and position >= 0 and position <= 1:
            osc.sendMsg("/inst/bowed/bow", [pressure * 1.0, position * 1.0])

    # set vibrato frequency [Hz] and gain [0.0-1.0]
    def setVibrato(self, freq, gain):
        if gain >= 0 and gain <= 1:
            osc.sendMsg("/inst/bowed/vibratoFG", [freq * 1.0, gain * 1.0])

    # set volume [0.0-1.0]
    def setVolume(self, volume):
        if volume >= 0 and volume <= 1:
            osc.sendMsg("/inst/bowed/volume", [volume * 1.0])

    # start bowing [0.0-1.0]
    def startBowing(self, floatValue):
        if floatValue >= 0 and floatValue <= 1:
            osc.sendMsg("/inst/bowed/startBowing", [floatValue * 1.0])

    # stop bowing [0.0-1.0]
    def stopBowing(self, floatValue):
        if floatValue >= 0 and floatValue <= 1:
            osc.sendMsg("/inst/bowed/stopBowing", [floatValue * 1.0])


# reading from file (SndBuf)
class FileRead(Instrument):
    # initialize name, send message to ChucK to load the functions
    # for this particular instrument, time.sleep a bit for it to do so.
    def __init__(self):
        self.name = "sndbuf"
        osc.sendMsg("/init", [self.name])
        time.sleep(0.5)

    # read from file
    def setFile(self, filename):
        osc.sendMsg("/inst/sndbuf/read", [filename])

    # set read position
    def setReadPosition(self, position):
        if position >= 0:
            osc.sendMsg("/inst/sndbuf/position", [int(position)])

    # set playback rate
    def setPlaybackRate(self, rate):
        if rate > 0:
            osc.sendMsg("/inst/sndbuf/rate", [rate * 1.0])

    # set looping
    def setLooping(self, doLoop):
        if doLoop >= 0:
            osc.sendMsg("/inst/sndbuf/loop", [int(doLoop)])

    # set looping repetition rate
    def setLoopRate(self, loopsPerSecond):
        if loopsPerSecond > 0:
            osc.sendMsg("/inst/sndbuf/loopRate", [loopsPerSecond * 1.0])


# The end, for now
