Differences

This shows you the differences between two versions of the page.

Link to this comparison view

test-and-measurement:analog-discovery-studio:soda-can-theremin [2022/09/12 15:40] – changed forum.digilentinc.com to forum.digilent.com jeff.greenberg_ni.comtest-and-measurement:analog-discovery-studio:soda-can-theremin [2022/10/04 11:38] (current) – external edit 127.0.0.1
Line 1: Line 1:
 +====== Building a Soda Can Theremin with the Analog Discovery Studio ======
 +
 +~~TechArticle~~
 +
 +===== Introduction =====
 +<WRAP group><WRAP half column>
 +Remember those old-school sci-fi movies with squeaky and eerie background music? Those unique sounds were made using a Theremin, an electronic instrument that can be played without physical contact. When playing a Theremin, the operator can control volume and pitch with his or her hand position relative to two antennas.
 +
 +This project is going to demonstrate how to build and debug a simple Theremin on the Analog Discovery Studio using a soda can as the antenna! With simplicity in mind, the Theremin in this project will only demonstrate pitch modulation. The design that is implemented is shown in the block diagram to the right. It doesn't take too much extra work to add volume control, so I encourage you to try it if you're looking for an extra challenge.
 +</WRAP><WRAP half column>
 +{{ :electronics_explorer:block_diagram.png?600px }}
 +</WRAP></WRAP>
 +
 +<WRAP group>
 +To keep it short and sweet we won't get too deep into the theory of how a Theremin works. Briefly though, the antenna (the soda can) acts as one plate of a capacitor. Your hand will act as the other plate of the capacitor. Thus, by moving your hand nearer and further from the can, you are changing the capacitance of the circuit. This takes place within the variable-frequency oscillator portion of our circuit. As the capacitance in the oscillator changes, the oscillator's output frequency changes as well. This change in frequency can be heard at the speaker at the end of the circuit.
 +
 +In this project, two approaches will be discussed: first we will build the Theremin on the breadboard, debugging the circuit with the Analog Discovery Studio (this will be a fully analog circuit), then we will remove the signal processing part from the circuit and use the Analog Discovery Studio and WaveForms SDK to record, process and output the signal to the speaker (this will be a mixed signal circuit).
 +</WRAP>
 +----
 +
 +===== Inventory =====
 +<WRAP group><WRAP half column>
 +For the fully analog theremin:
 +  * [[test-and-measurement:analog-discovery-studio:start|Analog Discovery Studio]]
 +  * [[test-and-measurement:analog-discovery-studio:canvases:breadboard-canvas:start|Breadboard Canvas]]
 +  * [[https://digilent.com/shop/analog-parts-kit-by-analog-devices-companion-parts-kit-for-the-analog-discovery/|Analog Parts Kit]]
 +    * speaker
 +    * audio connector
 +    * OP27 operational amplifier (x2)
 +    * OP37 operational amplifier (x2)
 +    * 2N3904 NPN transistor
 +    * 2N3906 PNP transistor
 +    * 1N3064 small-signal diode
 +    * 1N914 diode (x2)
 +    * 10kΩ potentiometer
 +    * 100pF ceramic capacitor
 +    * 1nF ceramic capacitor (x2)
 +    * 10nF ceramic capacitor (x2)
 +    * 47nF ceramic capacitor
 +    * 100nF ceramic capacitor
 +    * 47Ω resistor (x2)
 +    * 470Ω resistor
 +    * 4.7kΩ resistor
 +    * 10kΩ resistor (x5)
 +    * 20kΩ resistor (x2)
 +    * 47kΩ resistor (x2)
 +    * 100kΩ resistor (x3)
 +  * a soda can
 +  * [[software:waveforms:waveforms-3:start]]
 +</WRAP><WRAP half column>
 +For the mixed-signal theremin:
 +  * [[test-and-measurement:analog-discovery-studio:start|Analog Discovery Studio]]
 +  * [[test-and-measurement:analog-discovery-studio:canvases:breadboard-canvas:start|Breadboard Canvas]]
 +  * [[https://digilent.com/shop/analog-parts-kit-by-analog-devices-companion-parts-kit-for-the-analog-discovery/|Analog Parts Kit]]
 +    * OP27 operational amplifier
 +    * 10kΩ potentiometer
 +    * 100pF ceramic capacitor
 +    * 100kΩ resistor (x3)
 +  * a soda can
 +  * [[software:waveforms:waveforms-3:start]]
 +  * [[https://www.python.org/downloads/|Python 3]]
 +</WRAP></WRAP>
 +----
 +
 +===== Building the Circuit =====
 +==== 1. Variable-Frequency Oscillator ====
 +<WRAP group>
 +For the variable-frequency oscillator, we will be using the OP27 operational amplifier. The pin out and specifications of the OP27 can be seen in its [[http://www.analog.com/media/en/technical-documentation/data-sheets/OP27.pdf|data sheet]].
 +</WRAP>
 +
 +<WRAP group><WRAP half column>
 +Place the op-amp such that it straddles the valley of the breadboard and the notch is towards the top of the breadboard. Op-amps require +5V at one rail and -5V at the other rail. That being said, one power rail of the breadboard is designated for +5V and the other power rail is designated for -5V. In the breadboard image below, red wires are used for connections to the +5V power rail and white wires are used for connections to the -5V power rail. Connect pin 7 of the op-amp to the positive power rail (+5V). Connect pin 4 of the op-amp to the negative power rail (-5V). Connect a black wire between the ground rail and one of the breadboard rows above the op-amp.
 +
 +Place the 100pF capacitor between the grounded row of the breadboard and pin 2 of the op-amp. Now we'll place the resistors. All the resistors used in the oscillator are 100kΩ. Place one resistor across the op-amp from pin 2 to pin 6. Place another resistor across the op-amp from pin 3 to pin 6. Place the last resistor between the grounded row of the breadboard and pin 3 of the op-amp.
 +</WRAP><WRAP half column>
 +{{ reference:test-and-measurement:analog-discovery-studio:analog_theremin_schem_1.png?600 |}}
 +</WRAP></WRAP>
 +
 +<WRAP group><WRAP half column>
 +The soda can is to be placed in parallel with the capacitor. Connect one end of a black jumper wire to the grounded rail of the breadboard. You will hold the other end of this wire in your right hand when operating the Theremin. Connect one end of a green jumper wire to pin 2 of the op-amp. Connect the other end of this wire to the soda can. A slightly longer jumper wire is easier here. I place the wire such that it is held on by the tab on the top of the can. Taping the wire to the can would work as well. Just make sure there is metal-to-metal contact. By holding the black wire in your right hand, your body is now part of the circuit. This allows you to use your left hand to act as a plate of a capacitor with the soda can.
 +
 +Connect an orange wire to pin 6 of the op-amp. This will carry the output voltage from this oscillator to the next section of the circuit, the weighted summer.
 +</WRAP><WRAP half column>
 +{{ reference:test-and-measurement:analog-discovery-studio:analog_theremin_bb_1.png?400 |}}
 +</WRAP></WRAP>
 +
 +<WRAP group><WRAP half column>
 +To see the output of this circuit block, open WaveForms, then, in the //Supplies// instrument, set +5V, respectively -5V for the variable power supplies. Enable both power supplies, then turn on the **V±** switch on the Breadboard Canvas, to power on the circuit.
 +
 +Connect the Oscilloscope Channel 1+ (orange wire) to the output of the block and the 1- (orange-white wire) to ground, then open and start the //Scope// instrument. As you move your hand towards the soda can, you should see the oscillation frequency changing.
 +</WRAP><WRAP half column>
 +{{ reference:test-and-measurement:analog-discovery-studio:variable_frequency_oscillator_test.gif?600 |}}
 +</WRAP></WRAP>
 +----
 +
 +==== 2. Fixed-Frequency Oscillator ====
 +<WRAP group>
 +The output of the fixed-frequency oscillator should be a sinusoidal wave with a frequency close to that of the variable-frequency oscillator. In this project, that is about 32 kHz. To implement this, we can use the [[https://www.analog.com/media/en/technical-documentation/data-sheets/AD654.pdf|AD654]] voltage to frequency converter from the Analog Parts Kit and a low-pass filter network to filter out higher frequency harmonics from the generated square wave.
 +</WRAP>
 +
 +<WRAP group><WRAP half column>
 +For the fixed-frequency oscillator, we will be using the OP37 operational amplifier. The pin out and specifications of the OP37 can be seen in the [[https://www.analog.com/media/en/technical-documentation/data-sheets/OP37.pdf|data sheet]].
 +
 +Place the op-amp such that it straddles the valley of the breadboard and the notch is towards the top of the breadboard.
 +Op-amps require +5V at one rail and -5V at the other rail. That being said, one power rail of the breadboard is designated for +5V and the other power rail is designated for -5V. In the breadboard image below, red wires are used for connections to the +5V power rail and white wires are used for connections to the -5V power rail. Connect pin 7 of the op-amp to the positive power rail (+5V). Connect pin 4 of the op-amp to the negative power rail (-5V).
 +</WRAP><WRAP half column>
 +{{ reference:test-and-measurement:analog-discovery-studio:analog_theremin_schem_2.png?600 |}}
 +</WRAP></WRAP>
 +
 +<WRAP group><WRAP half column>
 +Place the 1nF capacitor between the grounded row of the breadboard and pin 2 of the op-amp. Place one 10kΩ resistor across the op-amp from pin 2 to pin 6. Place another 10kΩ resistor across the op-amp from pin 3 to pin 6. Place the 20kΩ resistor between the grounded row of the breadboard and pin 3 of the op-amp.
 +
 +Connect an orange wire to pin 6 of the op-amp. This will carry the output voltage from this oscillator to the low-pass filter network. This network consists of three RC low-pass filters, connected in series, the first being connected to the output of the op-amp (pin 6). The output of this block is taken from the ungrounded pin of the last capacitor.
 +</WRAP><WRAP half column>
 +{{ reference:test-and-measurement:analog-discovery-studio:analog_theremin_bb_2.png?400 |}}
 +</WRAP></WRAP>
 +
 +<WRAP group><WRAP half column>
 +To view the signals on different parts of this block, power on the circuit, as previously described, then connect the Oscilloscope Channel 1+ (orange wire) to the output of the op-amp (pin 6) and Channel 2+ (blue wire) to the output of the low-pass filter network. Don't forget to ground Channels 1- (orange-white wire) and 2- (blue-white wire).
 +
 +As seen in the image to the right, the output signal is more a triangular wave, than a sinus, but for this application, this will be a good approximation.
 +</WRAP><WRAP half column>
 +{{ reference:test-and-measurement:analog-discovery-studio:fixed_frequency_oscillator_testing.png?600 |}}
 +</WRAP></WRAP>
 +----
 +
 +==== 3. Mixer ====
 +<WRAP>
 +This next portion of the circuit, the weighted summer, mixes the signals from our fixed and variable-frequency oscillators. We will be using another OP27.
 +</WRAP>
 +
 +<WRAP group><WRAP half column>
 +Move a few holes down the breadboard from the oscillator and place the OP27 such that it straddles the valley of the board and the notched side is towards the left side of the board. Just as we did when building the oscillator, connect pin 7 of the op-amp to the positive power rail and pin 4 of the op-amp to the negative power rail. Connect pin 3 of the op-amp to one of the ground rails on the breadboard.
 +</WRAP><WRAP half column>
 +{{ reference:test-and-measurement:analog-discovery-studio:analog_theremin_schem_3.png?600 |}}
 +</WRAP></WRAP>
 +
 +<WRAP group><WRAP half column>
 +Connect two 47kΩ resistors in parallel as shown in the breadboard image below. The bottom side of both resistors should be connected to pin 2 of the op-amp. The left sides of the resistors should be off by one row as shown. Connect the other end of the orange wire from the oscillator to one of these 47kΩ resistors. Use another wire to connect the output of the low-pass filter network after the fixed-frequency oscillator to the other 47kΩ resistor. Place a 20kΩ resistor across the op-amp from pin 2 to pin 6. Lastly, place an orange wire at pin 6 of the op-amp, just like in step 1. This will connect the output of the weighted summer to the next section of the circuit, the envelope detector.
 +</WRAP><WRAP half column>
 +{{ reference:test-and-measurement:analog-discovery-studio:analog_theremin_bb_3.png?400 |}}
 +</WRAP></WRAP>
 +
 +<WRAP group><WRAP half column>
 +As the frequencies of the two input signals are very close, when they are added, they interfere, producing a beat (a specific interference pattern). At this point the output signal is still outside the audible frequency domain, but it can be visualized with the oscilloscope.
 +</WRAP><WRAP half column>
 +{{ reference:test-and-measurement:analog-discovery-studio:mixer_test.gif?600 |}}
 +</WRAP></WRAP>
 +----
 +
 +==== 4. Envelope Detector ====
 +<WRAP>
 +The new signal output from the weighted summer will be passed through an envelope detector which traces the beating pattern.
 +
 +**Note:** //The orientation of the diode is very important. The diode in the Analog Parts Kit is orange with a black band on one side. The side with the black band is “negative”.//
 +</WRAP>
 +
 +<WRAP group><WRAP half column>
 +The input of the envelope detector is the output of the weighted summer. A few holes down from the weighted summer, connect the other end of the orange wire (from the weighted summer) to the orange ("positive") side of the diode. At the negative side of the diode, place the 10kΩ resistor and 47nF capacitor in parallel. Connect the other side of the resistor and capacitor to a ground rail.
 +</WRAP><WRAP half column>
 +{{ reference:test-and-measurement:analog-discovery-studio:analog_theremin_schem_4.png?600 |}}
 +</WRAP></WRAP>
 +
 +<WRAP group><WRAP half column>
 +The output from this circuit is at the junction of the diode, resistor, and capacitor. Connect one side of an orange wire here.
 +</WRAP><WRAP half column>
 +{{ reference:test-and-measurement:analog-discovery-studio:analog_theremin_bb_4.png?200 |}}
 +</WRAP></WRAP>
 +
 +<WRAP group><WRAP half column>
 +The output signal falls in the audible domain. However, it is "weak", having a small amplitude, but a relatively large DC offset, which can damage the speaker, so further processing is necessary.
 +</WRAP><WRAP half column>
 +{{ reference:test-and-measurement:analog-discovery-studio:envelope_detector_test.png?600 |}}
 +</WRAP></WRAP>
 +----
 +
 +==== 5. Amplifier ====
 +<WRAP group>
 +The amplifier used here is an inverting amplifier built with an OP37 op-amp. The OP37 has an identical pin out compared to the OP27. It behaves similarly as well. If you're interested though, [[https://www.analog.com/media/en/technical-documentation/data-sheets/OP37.pdf|here]] is the data sheet specific to the OP37.
 +</WRAP>
 +
 +<WRAP group><WRAP half column>
 +Moving moving further down the board, place the OP37 the same way the OP27's were placed, straddling the valley of the board with the notch to towards the top of the breadboard. As with the previous two op-amps, connect pin 7 of the op-amp to the +5V rail of the breadboard and connect pin 4 of the op-amp to the -5V rail of the breadboard.
 +
 +Place a 10kΩ potentiometer near the op-amp. This potentiometer will be used to set the amplitude (volume) of the signal. Connect the middle pin to pin 2 of the op-amp and another pin to pin 6 of the op-amp. Place a 4.7kΩ resistor such that one side of it is connected at pin 2 of the op-amp. The other side of the resistor should be connected a few holes to above the op-amp. Connect the other side of the orange wire from the envelope detector's output to this side of the resistor.
 +</WRAP><WRAP half column>
 +{{ reference:test-and-measurement:analog-discovery-studio:analog_theremin_schem_5.png?600 |}}
 +</WRAP></WRAP>
 +
 +<WRAP group><WRAP half column>
 +Pin 3 of the op-amp would normally be connected to ground through a resistor. In this scenario, it is ideal to use as large of a resistor as possible. To simulate an infinite resistance, we will leave pin 3 unconnected such that it sees an “open circuit”. No current can flow through an open circuit it so it acts as an infinite resistance.
 +
 +Connect the 100nF capacitor to the output of the op-amp. Connect an orange wire to the other pin of the capacitor. This will be used to send the amplifier to the speaker, while the capacitor in series with the output will eliminate the DC offset of the signal (decoupling capacitor).
 +</WRAP><WRAP half column>
 +{{ reference:test-and-measurement:analog-discovery-studio:analog_theremin_bb_5.png?400 |}}
 +</WRAP></WRAP>
 +
 +<WRAP group><WRAP half column>
 +To visualize the signals, connect the Oscilloscope Channel 1+ (orange wire) to the input of the block and Channel 2+ (blue wire) to the output, after the decoupling capacitor. Don't forget to ground the 1- (orange-white) and 2- (blue-white) wires.
 +</WRAP><WRAP half column>
 +{{ reference:test-and-measurement:analog-discovery-studio:amplifier_test.png?600 |}}
 +</WRAP></WRAP>
 +----
 +
 +==== 6. Power Amplifier ====
 +<WRAP group>
 +At this point you can connect the speaker, or the audio connector between the output of the last block and ground, or you can follow the steps further to add a power amplifier circuit to before the speaker, which will make your "music" much louder. If you intend to use a separate audio amplifier (maybe built in the speaker), you can leave this part out.
 +
 +As the output current of the op-amp should be kept as low as possible (below ±10mA according to the datasheet), but a 8Ω speaker would draw more than 100mA current at a 1V amplitude signal (our signal can have an amplitude even higher than 1V), to reach maximum loudness, a class AB power amplifier can be used to supply the necessary current.
 +</WRAP>
 +
 +<WRAP group><WRAP half column>
 +Connect together the emitters of an NPN and a PNP transistor. Connect the collector of the NPN transistor to +5V, the collector of the PNP transistor to -5V. Connect one 10KΩ resistor between the base and the collector of each transistor.
 +</WRAP><WRAP half column>
 +{{ reference:test-and-measurement:analog-discovery-studio:analog_theremin_schem_6.png?600 |}}
 +</WRAP></WRAP>
 +
 +<WRAP group><WRAP half column>
 +Connect the anode ("positive" pin) of a diode to the base of the NPN transistor. Connect the anode of a second diode to the cathode of the first, and the cathode of the second diode to the base of the PNP transistor.
 +
 +The input of the power amplifier is the cathode of the first diode and the output is the emitter of the transistors.
 +</WRAP><WRAP half column>
 +{{ reference:test-and-measurement:analog-discovery-studio:analog_theremin_bb_6.png?400 |}}
 +</WRAP></WRAP>
 +----
 +
 +==== Review of the Whole Circuit ====
 +<WRAP group>
 +A schematic and a breadboard diagram of the whole circuit can be found below. The Fritzing diagram can be downloaded {{ reference:test-and-measurement:analog-discovery-studio:theremin.zip |here}}.
 +
 +{{ reference:test-and-measurement:analog-discovery-studio:analog_theremin_schem.png?600 |}}
 +
 +{{ reference:test-and-measurement:analog-discovery-studio:analog_theremin_bb.png?600 |}}
 +</WRAP>
 +
 +<WRAP group>
 +To use the theremin, open WaveForms, open the //Supplies// instrument, enable the positive and negative variable supplies and set their voltage to +5V, respectively -5V. Turn on the switch on the Breadboard Canvas. You can use a small screwdriver to set the loudness of the theremin with the trimmer potentiometer.
 +</WRAP>
 +----
 +
 +===== Reducing the Circuit and Writing the Software =====
 +<WRAP group>
 +This time the theremin will be implemented mostly from software: we will use Python 3 and WaveForms SDK to record the output of the variable-frequency oscillator, generate the output of the fixed-frequency oscillator (virtually), and mix the two signals. The envelope detector and the amplifier will also be implemented in software.
 +
 +**Note:** //If you are not familiar with using WaveForms SDK in Python, check this guide: [[test-and-measurement:guides:waveforms-sdk-getting-started]].//
 +</WRAP>
 +
 +==== 1. Modifying the Circuit ====
 +<WRAP group>
 +As the Analog Discovery Studio can't measure capacitance directly (the impedance analyzer can measure capacitance but it needs a waveform generator channel and is too slow for this application), we still need the first block of the previous circuit (see the [[test-and-measurement:analog-discovery-studio:soda-can-theremin#variable-frequency_oscillator|Variable-Frequency Oscillator]] section). You won't need the other parts of the circuit.
 +
 +Connect the Oscilloscope Channel 1+ (orange wire) to the output of the oscillator and ground Channel 1- (orange-white wire).
 +</WRAP>
 +
 +<WRAP group><WRAP half column>
 +Connect the Oscilloscope Channel 2+ (blue wire) to the middle pin of the 10kΩ potentiometer and ground Channel 2- (blue-white wire). Use jumper wires to connect one unconnected leg of the potentiometer to the positive supply voltage and the other to the ground. This potentiometer will set the volume of the theremin.
 +</WRAP><WRAP half column>
 +{{ reference:test-and-measurement:analog-discovery-studio:potentiometer_zoomed.png?400 |}}
 +</WRAP></WRAP>
 +
 +----
 +
 +==== 2. Modules ====
 +<WRAP group>
 +In the following, separate python modules will be created for controlling the Test & Measurement device and different instruments. The full source code of the project can be downloaded {{ reference:test-and-measurement:analog-discovery-studio:theremin_py.zip |here}}.
 +</WRAP>
 +
 +=== Device ===
 +<WRAP group>
 +This module contains functions which load the WaveForms SDK and connect and disconnect the Analog Discovery Studio.
 +
 +--> device.py #
 +<code python>
 +"""
 +    Module containing functions for controlling the device connection and status
 +"""
 +
 +# import necessary modules
 +import ctypes as ctp
 +import sys
 +
 +
 +class state:
 +    # device parameters
 +    dwf = None
 +    hdwf = None
 +    error = False
 +    error_msg = None
 +
 +
 +def load():
 +    # load the WaveForms SDK
 +    if sys.platform.startswith("win"):
 +        state.dwf = ctp.cdll.dwf
 +    elif sys.platform.startswith("darwin"):
 +        state.dwf = ctp.cdll.LoadLibrary(
 +            "/Library/Frameworks/dwf.framework/dwf")
 +    else:
 +        state.dwf = ctp.cdll.LoadLibrary("libdwf.so")
 +
 +    # check for errors
 +    check_error()
 +    if state.error:
 +        print(state.error_msg)
 +        quit()
 +    return state.dwf
 +
 +
 +def check_error():
 +    # set error flag and error message if necessary
 +    szerr = ctp.create_string_buffer(512)
 +    state.dwf.FDwfGetLastErrorMsg(szerr)
 +    if szerr[0] != b'\0':
 +        state.error = True
 +        state.error_msg = str(szerr.value)
 +    return
 +
 +
 +def open():
 +    # count devices
 +    cDevice = ctp.c_int()
 +    state.dwf.FDwfEnum(ctp.c_int(0), ctp.byref(cDevice))
 +    if cDevice.value <= 0:
 +        state.error = True
 +        state.error_msg = "No device is connected"
 +        print(state.error_msg)
 +        quit()
 +
 +    # search for Analog Discovery Studio
 +    devicename = ctp.create_string_buffer(64)
 +    index = -1
 +    for iDevice in range(0, cDevice.value):
 +        state.dwf.FDwfEnumDeviceName(ctp.c_int(iDevice), devicename)
 +        if str(devicename.value) == "b'Analog Discovery Studio'":
 +            index = iDevice
 +            break
 +    if index < 0:
 +        state.error = True
 +        state.error_msg = "Analog Discovery Studio isn't connected"
 +        print(state.error_msg)
 +        quit()
 +
 +    # open device
 +    hdwf = ctp.c_int()
 +    state.dwf.FDwfDeviceOpen(ctp.c_int(index), ctp.byref(hdwf))
 +    state.hdwf = hdwf
 +    check_error()
 +    if state.error:
 +        print(state.error_msg)
 +        quit()
 +
 +    # set dynamic auto configuration
 +    state.dwf.FDwfDeviceAutoConfigureSet(state.hdwf, ctp.c_int(3))
 +    return state.hdwf
 +
 +
 +def close():
 +    # reset every instrument
 +    state.dwf.FDwfAnalogInReset(state.hdwf)
 +    state.dwf.FDwfAnalogOutReset(state.hdwf, ctp.c_int(-1))
 +    state.dwf.FDwfAnalogIOReset(state.hdwf)
 +    state.dwf.FDwfDigitalInReset(state.hdwf)
 +    state.dwf.FDwfDigitalOutReset(state.hdwf)
 +    state.dwf.FDwfDigitalIOReset(state.hdwf)
 +
 +    # close the device
 +    state.dwf.FDwfDeviceClose(state.hdwf)
 +    return
 +
 +</code>
 +<--
 +</WRAP>
 +
 +=== Supplies ===
 +<WRAP group>
 +This module contains functions which set the positive and negative supply voltages and turn on and off the supplies.
 +
 +--> supply.py #
 +<code python>
 +"""
 +    Module containing functions for controlling the power supplies
 +"""
 +
 +# import the necessary module
 +import ctypes as ctp
 +
 +# these will be initialized in the main program
 +dwf = None
 +hdwf = None
 +
 +
 +def enable(state):
 +    # power supply master switch
 +    dwf.FDwfAnalogIOEnableSet(hdwf, ctp.c_int(state))
 +    return
 +
 +
 +def initialize(positive_voltage, negative_voltage):
 +    # enable variable supplies and set voltages
 +    dwf.FDwfAnalogIOChannelNodeSet(
 +        hdwf, ctp.c_int(0), ctp.c_int(0), ctp.c_double(True))
 +    dwf.FDwfAnalogIOChannelNodeSet(hdwf, ctp.c_int(
 +        0), ctp.c_int(1), ctp.c_double(positive_voltage))
 +    dwf.FDwfAnalogIOChannelNodeSet(
 +        hdwf, ctp.c_int(1), ctp.c_int(0), ctp.c_double(True))
 +    dwf.FDwfAnalogIOChannelNodeSet(hdwf, ctp.c_int(
 +        1), ctp.c_int(1), ctp.c_double(negative_voltage))
 +    return
 +
 +</code>
 +<--
 +</WRAP>
 +
 +=== Oscilloscope ===
 +<WRAP group>
 +This module contains functions which initialize the oscilloscope and record data.
 +
 +--> scope.py #
 +<code python>
 +"""
 +    Module containing functions for controlling the Oscilloscope
 +"""
 +
 +# import modules, constants and types
 +import dwfconstants as dwfct
 +import ctypes as ctp
 +import time
 +
 +# these will be initialized in the main program
 +dwf = None
 +hdwf = None
 +
 +
 +class state:
 +    # instrument parameters
 +    sampling_frequency = 1000000.0
 +    buffer_size = 8192
 +
 +
 +def initialize():
 +    # initialize the oscilloscope
 +    # set sampling frequency
 +    dwf.FDwfAnalogInFrequencySet(hdwf, ctp.c_double(state.sampling_frequency))
 +    # set buffer size
 +    dwf.FDwfAnalogInBufferSizeSet(hdwf, ctp.c_int(state.buffer_size))
 +    # enable both channels
 +    dwf.FDwfAnalogInChannelEnableSet(hdwf, ctp.c_int(-1), ctp.c_bool(True))
 +    # set range from -5 to +5 V
 +    dwf.FDwfAnalogInChannelRangeSet(hdwf, ctp.c_int(-1), ctp.c_double(5))
 +    # store every n-th conversion (don't average)
 +    dwf.FDwfAnalogInChannelFilterSet(hdwf, ctp.c_int(-1), dwfct.filterDecimate)
 +    # turn off the trigger
 +    dwf.FDwfAnalogInTriggerSourceSet(hdwf, dwfct.trigsrcNone)
 +    time.sleep(2)
 +    return
 +
 +
 +def acquisition(channel):
 +    # data acquisition
 +    sts = ctp.c_byte()
 +    rgdSamples = (ctp.c_double*state.buffer_size)()
 +    # start acquisition
 +    dwf.FDwfAnalogInConfigure(hdwf, ctp.c_int(1), ctp.c_int(1))
 +    while True:
 +        # wait for it to finish
 +        dwf.FDwfAnalogInStatus(hdwf, ctp.c_int(1), ctp.byref(sts))
 +        if sts.value == dwfct.DwfStateDone.value:
 +            break
 +    # get data
 +    dwf.FDwfAnalogInStatusData(
 +        hdwf, channel - 1, rgdSamples, state.buffer_size)
 +    return rgdSamples
 +
 +</code>
 +<--
 +</WRAP>
 +
 +=== Waveform Generator ===
 +<WRAP group>
 +This module contains functions which initialize the waveform generator and generate custom signals.
 +
 +--> wavegen.py #
 +<code python>
 +"""
 +    Module containing functions for controlling the Waveform Generator
 +"""
 +
 +# import modules, constants and types
 +import dwfconstants as dwfct
 +import ctypes as ctp
 +import time
 +
 +# these will be initialized in the main program
 +dwf = None
 +hdwf = None
 +
 +
 +class state:
 +    # instrument parameters
 +    sampling_frequency = 1000000.0
 +    buffer_size = 8192
 +    frame_repeat = 1
 +
 +
 +def initialize():
 +    # initialize the instrument
 +    frequency = state.sampling_frequency / (1.0 * state.buffer_size)
 +    # enable wavegen
 +    dwf.FDwfAnalogOutNodeEnableSet(
 +        hdwf, ctp.c_int(-1), dwfct.AnalogOutNodeCarrier, ctp.c_bool(True))
 +    dwf.FDwfAnalogOutNodeFunctionSet(
 +        hdwf, ctp.c_int(-1), dwfct.AnalogOutNodeCarrier, dwfct.funcCustom)
 +    # set frequency
 +    dwf.FDwfAnalogOutNodeFrequencySet(
 +        hdwf, ctp.c_int(-1), dwfct.AnalogOutNodeCarrier, ctp.c_double(frequency))
 +    # set run time
 +    dwf.FDwfAnalogOutRunSet(hdwf, ctp.c_int(-1), ctp.c_double(1.0 / frequency))
 +    # turn off wait
 +    dwf.FDwfAnalogOutWaitSet(hdwf, ctp.c_int(-1), ctp.c_double(0))
 +    # set repeat times
 +    dwf.FDwfAnalogOutRepeatSet(
 +        hdwf, ctp.c_int(-1), ctp.c_int(state.frame_repeat))
 +    return
 +
 +
 +def generate(signal, amplitude):
 +    # generate a signal
 +    # load the buffer
 +    dwf.FDwfAnalogOutNodeDataSet(hdwf, ctp.c_int(-1), dwfct.AnalogOutNodeCarrier,
 +                                 (ctp.c_double * len(signal))(*signal), ctp.c_int(len(signal)))
 +    # set the amplitude
 +    dwf.FDwfAnalogOutNodeAmplitudeSet(
 +        hdwf, ctp.c_int(-1), dwfct.AnalogOutNodeCarrier, ctp.c_double(amplitude))
 +    # start generation
 +    dwf.FDwfAnalogOutConfigure(hdwf, ctp.c_int(-1), ctp.c_int(1))
 +    return
 +
 +</code>
 +<--
 +</WRAP>
 +----
 +
 +==== 3. Main Script ====
 +<WRAP group><WRAP half column>
 +The main part of the project starts with importing the necessary modules, then defining the most important parameters of the project:
 +  * sampling_frequency - the frequency at which analog data is sampled
 +  * buffer_size - the number of data point in a data set (maximum is 8192)
 +  * amplitude - this sets the amplitude of the output signal, set it in a way to get around 1V amplitude at the output
 +  * frame_repeat - how many times should a data set be repeated
 +  * fixed_frequency - the frequency of the fixed-frequency oscillator
 +
 +These parameters, the device handle and the library of the SDK are also initialized in the modules.
 +</WRAP><WRAP half column><code python>
 +# import necessary modules
 +import sys
 +import math
 +import numpy
 +import ctypes as ctp
 +import dwfconstants as dwfct
 +from scipy.signal import hilbert
 +
 +# import own modules
 +import device
 +import supply
 +import scope
 +import wavegen
 +
 +
 +class state:
 +    # application parameters
 +    sampling_frequency = 1000000.0
 +    buffer_size = 8192
 +    amplitude = 2.0
 +    frame_repeat = 5
 +    fixed_frequency = 30500.0
 +
 +
 +# set parameters in modules
 +scope.state.sampling_frequency = state.sampling_frequency
 +wavegen.state.sampling_frequency = state.sampling_frequency
 +scope.state.buffer_size = state.buffer_size
 +wavegen.state.buffer_size = state.buffer_size
 +wavegen.state.frame_repeat = state.frame_repeat
 +
 +# load dynamic the SDK
 +dwf = device.load()
 +supply.dwf = dwf
 +scope.dwf = dwf
 +wavegen.dwf = dwf
 +
 +# open device, send handle to modules
 +hdwf = device.open()
 +supply.hdwf = hdwf
 +scope.hdwf = hdwf
 +wavegen.hdwf = hdwf
 +
 +</code></WRAP></WRAP>
 +
 +<WRAP group><WRAP half column>
 +After this, the instruments are initialized.
 +
 +The output of the fixed-frequency oscillator is generated. This is always the same, based on the following formula: $signal_{f_{fixed}}=sin(2*{\pi}*\frac{f_{fundamental}}{f_{sampling}}*(0:nr_{sample}))$
 +</WRAP><WRAP half column><code python>
 +try:
 +    # setup instruments
 +    supply.initialize(5.0, -5.0)
 +    supply.enable(True)
 +    scope.initialize()
 +    wavegen.initialize()
 +
 +    # generate fixed-frequency sine wave
 +    samples = numpy.arange(0, state.buffer_size - 1)
 +    angular_velocity = 2.0 * math.pi * state.fixed_frequency / state.sampling_frequency
 +    fixed_signal = samples * 1.0
 +    for index in range(len(samples)):
 +        fixed_signal[index] = math.sin(angular_velocity * samples[index])
 +    mixed_signal = samples * 1.0
 +
 +</code></WRAP></WRAP>
 +
 +<WRAP group><WRAP half column>
 +In the main loop the data acquired on Scope Channel 1 is normalized, then added (mixed) with the generated fixed-frequency signal. The envelope of the resulting signal is extracted by calculating the absolute value of the signal's Hilbert transform. The DC offset of the envelope is removed by substracting from the samples the average of the samples, then the resulting signal is normalized again.
 +
 +The second Oscilloscope Channel is read and averaged to get the potentiometer state, which determines the volume (amplitude) of the resulting signal.
 +
 +As a final step, the signal is generated with the desired amplitude on both Wavegen Channels.
 +
 +<WRAP round tip>
 +You can use the audio output (3.5 mm jack plug) on the Analog Discovery Studio as output. It also has an in-built amplifier, able to supply ±250 mA current.
 +</WRAP></WRAP><WRAP half column><code python>
 +# loop
 +    while True:
 +        # mix signals
 +        variable_signal = scope.acquisition(1)
 +        variable_maximum = max(variable_signal)
 +        for index in range(len(fixed_signal)):
 +            mixed_signal[index] = fixed_signal[index] + \
 +                (variable_signal[index] / variable_maximum)
 +
 +        # detect the envelope
 +        analytic_signal = hilbert(mixed_signal)
 +        amplitude_envelope = numpy.abs(analytic_signal)
 +
 +        # remove offset and limit the resulting values between +1 and -1
 +        envelope_average = sum(amplitude_envelope) / len(amplitude_envelope)
 +        ac_envelope = amplitude_envelope - envelope_average
 +        envelope_maximum = max(ac_envelope)
 +        ac_envelope = ac_envelope / envelope_maximum
 +
 +        # get potentiometer information and calculate volume
 +        voltages = scope.acquisition(2)
 +        voltage_average = sum(voltages) / len(voltages)
 +        volume = voltage_average * 1.0 / 5.0
 +
 +        # output the result on the waveform generator
 +        wavegen.generate(ac_envelope, volume * state.amplitude)
 +
 +</code></WRAP></WRAP>
 +
 +<WRAP group><WRAP half column>
 +You can stop the theremin by pressing Ctrl+C on the keyboard. The program will stop the supplies, reset all instruments and close the device, to make it available for other software (like WaveForms).
 +</WRAP><WRAP half column><code python>
 +except KeyboardInterrupt:
 +    # exit on ctrl+c
 +    pass
 +
 +finally:
 +    # stop the supplies and close the device
 +    supply.enable(False)
 +    device.close()
 +
 +</code></WRAP></WRAP>
 +----
 +
 +==== 4. Results ====
 +<WRAP group><WRAP half column>
 +If you want to visualize the signals outputted on the Wavegen Channels, connect an oscilloscope (for example another Test & Measurement device) to any output channel, or use the //matplotlib.pyplot// python module and the //plot()// function; however, plotting will introduce significant delays, thus the results may not be correct.
 +
 +<WRAP round tip>
 +You can output intermediate signals on the Wavegen Channels by sending the respective lists to the //wavegen.generate()// function.
 +</WRAP></WRAP><WRAP half column>
 +{{ reference:test-and-measurement:analog-discovery-studio:signal_shape.png?600 |}}
 +</WRAP></WRAP>
 +
 +<WRAP group><WRAP half column>
 +It can be seen, that if the variable frame_repeat is set to 1, huge delays appear in the output signal. This happens due to the processing speed of our script: mixing and demodulating/filtering the signals requires more time than the runtime of the waveform generator for a data set.
 +</WRAP><WRAP half column>
 +{{ reference:test-and-measurement:analog-discovery-studio:frame_repeat_1.png?600 |}}
 +</WRAP></WRAP>
 +
 +<WRAP group><WRAP half column>
 +This effect can be reduced by a small trick: we will repeat every data set a few times (around 5 is fine). As the signals are mostly periodic and because of the large sampling frequency and the reduced buffer size of the instrument, this trick isn't even audible in the resulting audio signal.
 +
 +Small (around 3ms) delays still are present. These are the moments when new data sets are loaded in the Wavegen's buffer, but these interruptions add a really cool effect to the resulting sound.
 +</WRAP><WRAP half column>
 +{{ reference:test-and-measurement:analog-discovery-studio:frame_repeat_5.png?600 |}}
 +</WRAP></WRAP>
 +----
 +
 +===== Comparing the Two Implementations =====
 +<WRAP group>
 +Both implementations have their advantages and draw-backs, so here comes a side-by-side comparison:
 +
 +=== Advantages ===
 +<WRAP group><WRAP half column>
 +**Analog Theremin**
 +  * continuous output signal
 +  * no delays present
 +  * fun to build
 +</WRAP><WRAP half column>
 +**Mixed-Signal Theremin**
 +  * much less components needed
 +  * more flexible (for example the fixed frequency is changeable)
 +  * easier to debug
 +  * fun to build
 +</WRAP></WRAP>
 +
 +=== Disadvantages ===
 +<WRAP group><WRAP half column>
 +**Analog Theremin**
 +  * many components needed
 +  * hard to debug
 +  * not flexible at all
 +  * sensible to noise (for example if you put your hand on one of the wires)
 +</WRAP><WRAP half column>
 +**Mixed-Signal Theremin**
 +  * small delays and drop-outs appear in the output (this might be wanted as sounds interesting)
 +</WRAP></WRAP>
 +</WRAP>
 +----
 +
 +===== Further Improvements =====
 +<WRAP group>
 +If you would like to play around more, adding another antenna to set the loudness and adding sliders/rotary encoders to tweak the sound of the mixed-signal theremin (you could dynamically set the sample rate, the frame repeat counter, and/or the fixed-frequency) are just a few ideas to set you off.
 +</WRAP>
 +----
 +
 +===== Next Steps =====
 +<WRAP group>
 +For more information on WaveForms SDK, see its [[software:waveforms:waveforms-sdk:start|Resource Center]].
 +
 +For technical support, please visit the [[https://forum.digilentinc.com/forum/8-test-and-measurement/|Test and Measurement]] section of the Digilent Forums.
 +</WRAP>