Information in this document is subject to change without notice. The example companies, organizations, products, people, and events depicted herein are fictitious. No association with any real company, organization, product, person or event is intended or should be inferred. Complying with all applicable copyright laws is the responsibility of the user. Without limiting the rights under copyright, no part of this document may be reproduced, stored in or introduced into a retrieval system, or transmitted in any form or by any means (electronic, mechanical, photocopying, recording, or otherwise), or for any purpose, without the express written permission of Microsoft Corporation.

 

Microsoft may have patents, patent applications, trademarked, copyrights, or other intellectual property rights covering subject matter in this document. Except as expressly provided in any written license agreement from Microsoft, the furnishing of this document does not give you any license to these patents, trademarks, copyrights, or other intellectual property.

 

© Microsoft Corporation. All rights reserved.

 

Microsoft, MS-DOS, MS, Windows, Windows NT, MSDN, Active Directory, BizTalk, SQL Server, SharePoint, Outlook, PowerPoint, FrontPage, Visual Basic, Visual C++, Visual J++, Visual InterDev, Visual SourceSafe, Visual C#, Visual J#,  and Visual Studio are either registered trademarks or trademarks of Microsoft Corporation in the U.S.A. and/or other countries.

 

Other product and company names herein may be the trademarks of their respective owners.


 

This source code is subject to terms and conditions of the Apache License, Version 2.0. A copy of the license can be found in the License.html file at the root of this distribution. If you cannot locate the Apache License, Version 2.0, please send an email to ironpy@microsoft.com. By using this source code in any fashion, you are agreeing to be bound by the terms of the Apache License, Version 2.0.

 

 

FMsynth

A frequency modulation synthesizer implemented in IronPython

Contributor: Matt Gruskin

 

Prerequisites

The prerequisites to successfully run FMSynth are:

Contents

Introduction

How To Use FMsynth

Design and Implementation

User Interface

The BufferPlayer Object

The WaveSource Object

Introduction

FMsynth is a frequency modulation synthesizer implemented in IronPython. Frequency modulation synthesis is a technique for combining audio waveforms to generate interesting musical tones and sound effects, used in many classic music synthesizers.

FMsynth demonstrates the use of many different technologies in IronPython.

How To Use FMsynth

Using FMsynth, you can define a frequency modulation synthesizer by combining any number of carrier and modulator wave sources together. You can then listen to your synthesizer by specifying a test frequency and clicking "Play", or by clicking a note on the piano keyboard. You can load and save synthesizers using XML files, by clicking on the "Load Synth" or "Save Synth" buttons.

A synthesizer is made up of carrier and modulator wave sources. A synthesizer can have any number of carriers, and each carrier can have any number of modulators, and each modulator can have any number of modulators. Modulators affect the frequency of the wave they are modulating - by adding modulators to wave sources you can create very interesting sounds!

Each wave source has a type which is "sine", "square", or "sawtooth" - these are the three wave types FMsynth knows how to generate. You can see the differences between these by playing a single carrier source set to one of these types, and observing the drawing of the waveform. Each wave source also has either an absolute frequency or a ratio. A ratio setting causes the wave source to change its frequency based on the base frequency selected. Ratios allow you to create synthesizers that will play different notes when different keys on the piano keyboard are clicked. Each waveform also has an amplitude, which affects the volume of a carrier with respect to the other carriers, or the magnitude of the effect of a modulator.

There are a number of sample synthesizers provided in the "synths\" folder. Some of these samples are musical in nature, while others use very low frequency modulators to create sound effects.

To run the synthesizer, simply issue the following command from the FMsynth folder:
    ipy.exe fmsynth.py

Design and Implementation

The source code for FMsynth is split into two Python source files.

User Interface

The user interface code is located in fmsynth.py. Some of the more interesting methods are described below.

There are also several methods which are event handlers for when user interface controls are changed, which are fairly self-explanatory.

The BufferPlayer Object

The BufferPlayer class is defined in fmsynthlib.py, and is responsible for managing DirectSound buffers as well as mixing all of the carrier signals of a synthesizer together. It also reads and writes XML files which contain synthesizer definitions.

The addBuffer() method creates a new DirectSound buffer to store sound data at the provided base frequency. It creates a DirectSound.WaveFormat object which is initialized to describe 1-channel, 16-bit, 44100 Hz PCM audio. It then creates a DirectSound.BufferDescription object, and sets the BufferBytes property to the desired buffer size. findGCDFreq() is used to calculate the smallest buffer size which will still be long enough to contain an entire cycle of audio for this particular synthesizer. Finally, a DirectSound.SecondayBuffer object is created, filled with audio data by genWaveSources(), and added to the bufs dictionary.

The genWaveSources() method generates audio data from each carrier wave source, normalizes and mixes them together, and then writes the audio data into the SecondaryBuffer. This method also fills the drawdata array which is later used to draw the audio data on screen. The audio data is mixed in two passes. In the first pass, each audio sample is summed together, and the maximum sample is found. In the second pass, the audio data is normalized using the maximum value found before, and is converted from floating point numbers to unsigned integers in the appropriate range for the DirectSound buffer. The final audio data is then written to the buffer using SecondaryBuffer.Write().

readXML() creates an XmlDocument object which is used to load the synthesizer XML file. FMsynth iterates through the child nodes of the BufferPlayer node. As it encounters WaveSource nodes, it creates new WaveSource objects and calls readXML() on these objects, having each WaveSource read its own properties from the XmlDocument. writeXML() works similarly, first writing the root BufferPlayer node and then having each WaveSource write itself to the XML file using an XmlTextWriter object.

The WaveSource Object

The WaveSource class is defined in fmsynthlib.py and is responsible for storing the parameters for each wave source as well as generating audio data. It also provides methods for reading and writing wave sources as XML data. The WaveSource class is a base class which is inherited by SineSource, SquareSource, and SawSource, which implement algorithms for generating the different wave source types.

Each WaveSource has a list of modulators, where each modulator is also a WaveSource. prepModData() generates the audio data for each modulation source, while addModData() combines and returns the modulation data for a given sample.

Each child class of WaveSource implements the genData() method, which generates the audio data. This method calculates the number of samples needed for a complete cycle, and then generates this many samples of data. It then uses fillData() to repeat that data for the full length of the buffer.