Release 4.2.0 tarball

This commit is contained in:
Gary Scavone
2013-09-29 23:39:37 +02:00
committed by Stephen Sinclair
parent fe20fe92a2
commit de344668dd
347 changed files with 16972 additions and 8538 deletions

View File

@@ -10,7 +10,7 @@
<HR>
<!-- Generated by Doxygen 1.3.4 -->
<h1><a class="anchor" name="controlin">Control Input</a></h1>Each Synthesis ToolKit instrument exposes its relevant control parameters via public functions such as setFrequency() and controlChange(). Programmers are free to implement the control scheme of their choice in exposing those parameters to the user.<p>
A text-based control protocol called <a href="skini.html"><a class="el" href="classSKINI.html">SKINI</a></a> is provided with the Synthesis ToolKit. <a class="el" href="classSKINI.html">SKINI</a> extends the MIDI protocol in incremental ways, providing a text-based messaging scheme in human-readable format and making use of floating-point numbers wherever possible. Each <a class="el" href="classSKINI.html">SKINI</a> message consists of a message type (e.g., NoteOn, PitchBend), a time specification (absolute or delta), a channel number (scanned as a long integer), and a maximum of two subsequent message-specific field values. Knowing this, it should be relatively clear what the following <a class="el" href="classSKINI.html">SKINI</a> "scorefile" specifies:<p>
A text-based control protocol called <a href="skini.html">SKINI</a> is provided with the Synthesis ToolKit. SKINI extends the MIDI protocol in incremental ways, providing a text-based messaging scheme in human-readable format and making use of floating-point numbers wherever possible. Each SKINI message consists of a message type (e.g., NoteOn, PitchBend), a time specification (absolute or delta), a channel number (scanned as a long integer), and a maximum of two subsequent message-specific field values. Knowing this, it should be relatively clear what the following SKINI "scorefile" specifies:<p>
<div class="fragment"><pre>NoteOn 0.000082 2 55.0 82.3
NoteOff 1.000000 2 55.0 64.0
NoteOn 0.000082 2 69.0 82.8
@@ -22,109 +22,196 @@ StringDetune 0.100000 2 22.0
StringDetune 0.100000 2 12.0
NoteOff 1.000000 2 69.0 64.0
</pre></div><p>
MIDI messages (with the exception of Sysex) are easily represented within the <a class="el" href="classSKINI.html">SKINI</a> protocol.<p>
The class <a class="el" href="classMessager.html">Messager</a> can be used to acquire and parse MIDI messages from a MIDI device and <a class="el" href="classSKINI.html">SKINI</a> messages from STDIN and socket connections. Many of the example programs included with the ToolKit distribution use a <a class="el" href="classMessager.html">Messager</a> instance to accept control input from the accompanying tcl/tk graphical user interfaces, from external MIDI devices, or from <a class="el" href="classSKINI.html">SKINI</a> scorefiles.<p>
In the following example, we'll modify the <code>bethree.cpp</code> program from the previous tutorial chapter and incorporate a <a class="el" href="classMessager.html">Messager</a> class to allow control via a <a class="el" href="classSKINI.html">SKINI</a> scorefile.<p>
<div class="fragment"><pre><span class="comment">// controlbee.cpp</span>
MIDI messages are easily represented within the SKINI protocol.<p>
The class <a class="el" href="classMessager.html">Messager</a> can be used to acquire and parse MIDI messages from a MIDI device and SKINI messages from STDIN and socket connections. Incoming messages are acquired asynchronously and saved to an internal message queue of <a class="el" href="structSkini_1_1Message.html">Skini::Message</a> types (MIDI messages are converted to the <a class="el" href="classSkini.html">Skini</a>:Message format). The user then uses the <a class="el" href="classMessager.html">Messager</a>:popMessage() function to retrieve incoming control messages. This function does not block, instead returning a message type of zero when no more messages are in the queue. Many of the example programs included with the ToolKit distribution use a <a class="el" href="classMessager.html">Messager</a> instance to accept control input from the accompanying tcl/tk graphical user interfaces, from external MIDI devices, or from SKINI scorefiles.<p>
In the following example, we'll modify the <code>bethree.cpp</code> program from the previous tutorial chapter and incorporate a <a class="el" href="classMessager.html">Messager</a> class to allow control via SKINI messages read from a SKINI file.<p>
<div class="fragment"><pre><span class="comment">// controlbee.cpp STK tutorial program</span>
<span class="preprocessor">#include "BeeThree.h"</span>
<span class="preprocessor">#include "RtWvOut.h"</span>
<span class="preprocessor">#include "RtAudio.h"</span>
<span class="preprocessor">#include "Messager.h"</span>
<span class="preprocessor">#include "SKINI.msg"</span>
<span class="preprocessor">#include &lt;math.h&gt;</span>
<span class="preprocessor">#include &lt;algorithm&gt;</span>
<span class="preprocessor">#if !defined(__OS_WINDOWS__) // Windoze bogosity for VC++ 6.0</span>
<span class="preprocessor"></span> <span class="keyword">using</span> std::min;
<span class="preprocessor">#endif</span>
<span class="preprocessor"></span>
<span class="keywordtype">void</span> usage(<span class="keywordtype">void</span>) {
<span class="comment">// Error function in case of incorrect command-line</span>
<span class="comment">// argument specifications.</span>
std::cout &lt;&lt; <span class="stringliteral">"\nuseage: controlbee file\n"</span>;
std::cout &lt;&lt; <span class="stringliteral">" where file = a SKINI scorefile.\n\n"</span>;
exit(0);
}
<span class="keywordtype">int</span> main()
<span class="comment">// The TickData structure holds all the class instances and data that</span>
<span class="comment">// are shared by the various processing functions.</span>
<span class="keyword">struct </span>TickData {
<a class="code" href="classInstrmnt.html">Instrmnt</a> *instrument;
<a class="code" href="classMessager.html">Messager</a> messager;
<a class="code" href="structSkini_1_1Message.html">Skini::Message</a> message;
<span class="keywordtype">int</span> counter;
<span class="keywordtype">bool</span> haveMessage;
<span class="keywordtype">bool</span> done;
<span class="comment">// Default constructor.</span>
TickData()
: instrument(0), counter(0), haveMessage(false), done( false ) {}
};
<span class="preprocessor">#define DELTA_CONTROL_TICKS 64 // default sample frames between control input checks</span>
<span class="preprocessor"></span>
<span class="comment">// The processMessage() function encapsulates the handling of control</span>
<span class="comment">// messages. It can be easily relocated within a program structure</span>
<span class="comment">// depending on the desired scheduling scheme.</span>
<span class="keywordtype">void</span> processMessage( TickData* data )
{
<span class="comment">// Set the global sample rate before creating class instances.</span>
<a class="code" href="classStk.html#e1">Stk::setSampleRate</a>( 44100.0 );
<span class="keyword">register</span> StkFloat value1 = data-&gt;message.floatValues[0];
<span class="keyword">register</span> StkFloat value2 = data-&gt;message.floatValues[1];
<a class="code" href="classInstrmnt.html">Instrmnt</a> *instrument = 0;
<a class="code" href="classRtWvOut.html">RtWvOut</a> *output = 0;
<a class="code" href="classMessager.html">Messager</a> *messager = 0;
<span class="keywordtype">bool</span> done = FALSE;
<span class="keywordflow">switch</span>( data-&gt;message.type ) {
<span class="keywordflow">case</span> __SK_Exit_:
data-&gt;done = <span class="keyword">true</span>;
<span class="keywordflow">return</span>;
<span class="keywordflow">case</span> __SK_NoteOn_:
<span class="keywordflow">if</span> ( value2 == 0.0 ) <span class="comment">// velocity is zero ... really a NoteOff</span>
data-&gt;instrument-&gt;noteOff( 0.5 );
<span class="keywordflow">else</span> { <span class="comment">// a NoteOn</span>
StkFloat frequency = 220.0 * pow( 2.0, (value1 - 57.0) / 12.0 );
data-&gt;instrument-&gt;noteOn( frequency, value2 * ONE_OVER_128 );
}
<span class="keywordflow">break</span>;
<span class="keywordflow">case</span> __SK_NoteOff_:
data-&gt;instrument-&gt;noteOff( value2 * ONE_OVER_128 );
<span class="keywordflow">break</span>;
<span class="keywordflow">case</span> __SK_ControlChange_:
data-&gt;instrument-&gt;controlChange( (<span class="keywordtype">int</span>) value1, value2 );
<span class="keywordflow">break</span>;
<span class="keywordflow">case</span> __SK_AfterTouch_:
data-&gt;instrument-&gt;controlChange( 128, value1 );
} <span class="comment">// end of switch</span>
data-&gt;haveMessage = <span class="keyword">false</span>;
<span class="keywordflow">return</span>;
}
<span class="comment">// This tick() function handles sample computation and scheduling of</span>
<span class="comment">// control updates. It will be called automatically when the system</span>
<span class="comment">// needs a new buffer of audio samples.</span>
<span class="keywordtype">int</span> tick(<span class="keywordtype">char</span> *buffer, <span class="keywordtype">int</span> bufferSize, <span class="keywordtype">void</span> *dataPointer)
{
TickData *data = (TickData *) dataPointer;
<span class="keyword">register</span> StkFloat *samples = (StkFloat *) buffer;
<span class="keywordtype">int</span> counter, nTicks = bufferSize;
<span class="keywordflow">while</span> ( nTicks &gt; 0 &amp;&amp; !data-&gt;done ) {
<span class="keywordflow">if</span> ( !data-&gt;haveMessage ) {
data-&gt;messager.popMessage( data-&gt;message );
<span class="keywordflow">if</span> ( data-&gt;message.type &gt; 0 ) {
data-&gt;counter = (<span class="keywordtype">long</span>) (data-&gt;message.time * <a class="code" href="classStk.html#e0">Stk::sampleRate</a>());
data-&gt;haveMessage = <span class="keyword">true</span>;
}
<span class="keywordflow">else</span>
data-&gt;counter = DELTA_CONTROL_TICKS;
}
counter = min( nTicks, data-&gt;counter );
data-&gt;counter -= counter;
<span class="keywordflow">for</span> ( <span class="keywordtype">int</span> i=0; i&lt;counter; i++ ) {
*samples++ = data-&gt;instrument-&gt;tick();
nTicks--;
}
<span class="keywordflow">if</span> ( nTicks == 0 ) <span class="keywordflow">break</span>;
<span class="comment">// Process control messages.</span>
<span class="keywordflow">if</span> ( data-&gt;haveMessage ) processMessage( data );
}
<span class="keywordflow">return</span> 0;
}
<span class="keywordtype">int</span> main( <span class="keywordtype">int</span> argc, <span class="keywordtype">char</span> *argv[] )
{
<span class="keywordflow">if</span> ( argc != 2 ) usage();
<span class="comment">// Set the global sample rate and rawwave path before creating class instances.</span>
<a class="code" href="classStk.html#e1">Stk::setSampleRate</a>( 44100.0 );
<a class="code" href="classStk.html#e3">Stk::setRawwavePath</a>( <span class="stringliteral">"../../rawwaves/"</span> );
TickData data;
<a class="code" href="classRtAudio.html">RtAudio</a> *dac = 0;
<span class="comment">// Figure out how many bytes in an StkFloat and setup the RtAudio object.</span>
RtAudioFormat format = ( <span class="keyword">sizeof</span>(StkFloat) == 8 ) ? RTAUDIO_FLOAT64 : RTAUDIO_FLOAT32;
<span class="keywordtype">int</span> bufferSize = RT_BUFFER_SIZE;
<span class="keywordflow">try</span> {
dac = <span class="keyword">new</span> <a class="code" href="classRtAudio.html">RtAudio</a>(0, 1, 0, 0, format, (<span class="keywordtype">int</span>)Stk::sampleRate(), &amp;bufferSize, 4);
}
<span class="keywordflow">catch</span> (<a class="code" href="classRtError.html">RtError</a>&amp; error) {
error.printMessage();
<span class="keywordflow">goto</span> cleanup;
}
<span class="keywordflow">try</span> {
<span class="comment">// Define and load the BeeThree instrument</span>
instrument = <span class="keyword">new</span> <a class="code" href="classBeeThree.html">BeeThree</a>();
<span class="comment">// Define and open the default realtime output device for one-channel playback</span>
output = <span class="keyword">new</span> <a class="code" href="classRtWvOut.html">RtWvOut</a>(1);
data.instrument = <span class="keyword">new</span> <a class="code" href="classBeeThree.html">BeeThree</a>();
}
<span class="keywordflow">catch</span> (<a class="code" href="classStkError.html">StkError</a> &amp;) {
<span class="keywordflow">goto</span> cleanup;
}
<span class="keywordflow">if</span> ( data.messager.setScoreFile( argv[1] ) == <span class="keyword">false</span> )
<span class="keywordflow">goto</span> cleanup;
<span class="keywordflow">try</span> {
<span class="comment">// Create a Messager instance to read from a redirected SKINI scorefile.</span>
messager = <span class="keyword">new</span> <a class="code" href="classMessager.html">Messager</a>();
dac-&gt;<a class="code" href="classRtAudio.html#a6">setStreamCallback</a>(&amp;tick, (<span class="keywordtype">void</span> *)&amp;data);
dac-&gt;<a class="code" href="classRtAudio.html#a13">startStream</a>();
}
<span class="keywordflow">catch</span> (<a class="code" href="classStkError.html">StkError</a> &amp;) {
<span class="keywordflow">catch</span> (<a class="code" href="classRtError.html">RtError</a> &amp;error) {
error.printMessage();
<span class="keywordflow">goto</span> cleanup;
}
<span class="comment">// Play the instrument until the end of the scorefile.</span>
<span class="keywordtype">int</span> i, nTicks, type;
MY_FLOAT byte2, byte3, frequency;
<span class="keywordflow">while</span> (!done) {
<span class="comment">// Look for new messages and return a delta time (in samples).</span>
type = messager-&gt;<a class="code" href="classMessager.html#a2">nextMessage</a>();
<span class="keywordflow">if</span> (type &lt; 0)
done = TRUE;
nTicks = messager-&gt;<a class="code" href="classMessager.html#a4">getDelta</a>();
<span class="keywordflow">try</span> {
<span class="keywordflow">for</span> ( i=0; i&lt;nTicks; i++ )
output-&gt;<a class="code" href="classRtWvOut.html#a6">tick</a>( instrument-&gt;<a class="code" href="classInstrmnt.html#a8">tick</a>() );
}
<span class="keywordflow">catch</span> (<a class="code" href="classStkError.html">StkError</a> &amp;) {
<span class="keywordflow">goto</span> cleanup;
}
<span class="keywordflow">if</span> ( type &gt; 0 ) {
<span class="comment">// Process the new control message.</span>
byte2 = messager-&gt;<a class="code" href="classMessager.html#a6">getByteTwo</a>();
byte3 = messager-&gt;<a class="code" href="classMessager.html#a7">getByteThree</a>();
<span class="keywordflow">switch</span>(type) {
<span class="keywordflow">case</span> __SK_NoteOn_:
frequency = (MY_FLOAT) 220.0 * pow( 2.0, (byte2 - 57.0) / 12.0 );
instrument-&gt;<a class="code" href="classInstrmnt.html#a2">noteOn</a>( frequency, byte3 * ONE_OVER_128 );
<span class="keywordflow">break</span>;
<span class="keywordflow">case</span> __SK_NoteOff_:
instrument-&gt;<a class="code" href="classInstrmnt.html#a3">noteOff</a>( byte3 * ONE_OVER_128 );
<span class="keywordflow">break</span>;
<span class="keywordflow">case</span> __SK_ControlChange_:
instrument-&gt;<a class="code" href="classInstrmnt.html#a10">controlChange</a>( (<span class="keywordtype">int</span>) byte2, byte3 );
<span class="keywordflow">break</span>;
<span class="keywordflow">case</span> __SK_AfterTouch_:
instrument-&gt;<a class="code" href="classInstrmnt.html#a10">controlChange</a>( 128, byte2 );
<span class="keywordflow">break</span>;
}
}
<span class="comment">// Block waiting until callback signals done.</span>
<span class="keywordflow">while</span> ( !data.done )
<a class="code" href="classStk.html#e7">Stk::sleep</a>( 100 );
<span class="comment">// Shut down the callback and output stream.</span>
<span class="keywordflow">try</span> {
dac-&gt;<a class="code" href="classRtAudio.html#a7">cancelStreamCallback</a>();
dac-&gt;<a class="code" href="classRtAudio.html#a12">closeStream</a>();
}
<span class="keywordflow">catch</span> (<a class="code" href="classRtError.html">RtError</a> &amp;error) {
error.printMessage();
}
cleanup:
<span class="keyword">delete</span> instrument;
<span class="keyword">delete</span> output;
<span class="keyword">delete</span> messager;
<span class="keyword">delete</span> data.instrument;
<span class="keyword">delete</span> dac;
<span class="keywordflow">return</span> 0;
}
</pre></div><p>
Assuming the program is compiled as <code>controlbee</code> and the <a class="el" href="classSKINI.html">SKINI</a> scorefile <a href="tutorial/bookert.ski"><code>bookert.ski</code></a> is in the <code>scores</code> directory, the scorefile could be redirected to the program as:<p>
<div class="fragment"><pre>controlbee &lt; scores/bookert.ski
A realtime control message will usually have a delta time of zero, in which case it is processed as soon as possible. Non-realtime messages, normally from a scorefile, will usually have non-zero delta times. The scheme used in this example is designed to work for both scorefile and realtime input types. When no message is available from the queue, the instrument is "ticked" for DELTA_CONTROL_TICKS and then the queue is checked again. The value of DELTA_CONTROL_TICKS roughly defines the program "control rate" in a realtime context, though multiple available messages in the queue are processed in immediate succession when their delta time values are zero.<p>
The <code>processMessage()</code> function centralizes the handling of control messages. Other control update schemes can be implemented, perhaps using a separate thread or in the <code>main()</code> function, and this function should work in any context.<p>
Assuming the program is compiled as <code>controlbee</code> and the SKINI scorefile <a href="tutorial/bookert.ski"><code>bookert.ski</code></a> is in the <code>scores</code> directory, the program can be run as:<p>
<div class="fragment"><pre>controlbee scores/bookert.ski
</pre></div><p>
Only a few basic <a class="el" href="classSKINI.html">SKINI</a> message type case statements are included in this example. It is easy to extend the program to support a much more elaborate set of instrument control parameters.<p>
This example could also be easily extended to accept "realtime" control input messages via STDIN, socket, or MIDI connections. The <a class="el" href="classMessager.html">Messager</a> class constructor takes an optional argument consisting of a bitmask of the following options: <code>STK_PIPE</code>, <code>STK_SOCKET</code>, and/or <code>STK_MIDI</code>.<p>
Only a few basic SKINI message type case statements are included in this example. It is easy to extend the program to support a much more elaborate set of instrument control parameters.<p>
This example could also be easily extended to accept "realtime" control input messages via pipe, socket or MIDI connections. The <a class="el" href="classMessager.html">Messager</a> class provides Messager::startStdInput(), Messager::startSocketInput(), and Messager::startMidiInput() functions for this purpose.<p>
[<a href="multichannel.html">Next tutorial</a>] &nbsp; [<a href="tutorial.html">Main tutorial page</a>] <HR>
<table>
<tr><td><A HREF="http://www-ccrma.stanford.edu/software/stk/"><I>The Synthesis ToolKit in C++ (STK)</I></A></td></tr>
<tr><td><A HREF="http://ccrma.stanford.edu/software/stk/"><I>The Synthesis ToolKit in C++ (STK)</I></A></td></tr>
<tr><td>&copy;1995-2004 Perry R. Cook and Gary P. Scavone. All Rights Reserved.</td></tr>
</table>