JavaFX drag and drop custom data

JavaFX has greatly simplified Drag and Drop over the mess that was in AWT and then given a zombified life through Swing.

To drag and drop custom data types, i.e. non string data, in Swing, you had to create a DataFlavor class with a MIME type. In JavaFX, all you have to do is create a simple DataFormat object.

1
DataFormat myformat = new DataFormat("some string that identifies your object");

And then use it like this in your setOnDragDetected handler.

1
2
3
4
5
6
Dragboard db = grandStaff.startDragAndDrop(TransferMode.ANY);
ClipboardContent content = new ClipboardContent();
//This is my class
MIDITrack track = grandStaff.getMIDITrack();
content.put(midiTrackDataFormat, track);
db.setContent(content);

You may get this problem when putting your object on the Dragboard in OSX:
“…is not a valid UTI string. Cannot set data for an invalid UTI.”

Digging through my XCode documentation on the OSX pasteboard, I found that the format HAS to be the usual reverse domain name class name we’ve been using for years.

1
2
 private static final DataFormat midiTrackDataFormat = new DataFormat(
            "com.rockhoppertech.music.midi.js.MIDITrack");

The thing is, the JavaFX documentation – and Oracle’s tutorial – makes no mention of this.

I hope this tidbit saves you some time.

Posted in JavaFX | Tagged , | Leave a comment

SuperCollider: drawing UGen graph

Many people find it useful to see graphically the network of unit generators in their SynthDefs. There is a Quark that will create a Graphviz .dot file from your SuperCollider code.

First, install Graphviz (of course). There are downloads for OSX, Linux, and others.

Then you need to install the rd_dot Quark. If you’ve never dealt with Quarks before, it is rather simple to install them. In your SuperCollider interpreter, type this:

Quarks.gui;

You will get a GUI window of all the available Quarks. The first time you run this the list will probably be empty. Press the Update Quark Listing button. Depending on your security settings, you may be prompted to accept their certificate. There will be a local listing written to your ~/Library/Application Support/SuperCollider/quarks directory. You might not get the listing refreshed immediately in your GUI windows after the process is finished. Simple kill the GUI and start it again.

Then, find the rd_dot quark. Click on the icon to the left of the name so it now reads +. Now press the Apply button in the “toolbar”. The rd_dot quark will be installed in your quarks directory.

Recompile your class library. It’s on the Language menu. Rebooting the interpreter will do it.

Try it out by changing the play method in your hacks to draw. Here’s a simple sine wave:

{SinOsc.ar()}.play;

Change play to draw:

{SinOsc.ar()}.draw;

When you interpret this now, you will get this displayed in Graphviz:

UGen graph

Possible glitch

If you have Word installed (I had a shotgun aimed at me), the file will open in Word since .dot files are associated with Word. The rd_dot quark writes a Graphviz .dot file to your tmp directory. An easy way to fix the association is to open any .dot file in the Finder inspector. Select it in Finder and press cmd-I. One of the sections in the inspector is Open With. Select SuperCollider from the option menu and apply to all.

If you want Word to remain associated with .dot files, you’ll have to hack the rd_dot code. It uses a simple call to open without any parameters, which is why Word is used. Change the open call to

open -a /Applications/SuperCollider/SuperCollider.app/
Posted in Computer Music, SuperCollider | Tagged , | 1 Response

SuperCollider with Emacs on OSX

SuperCollider (Github) has a decent IDE these days. But perhaps you’d like to use an actual editor like Emacs.

Sam Aaron published an article on this, but it is badly outdated. It was also overly complicated including copying several core directories out of the app. He’s probably too busy doing his good work with Overtone to update it. You should check out Overtone, by the way.

You need to get the current source for scmode. There is no separate repo, so you need the whole SuperCollider repo. Once you clone the repo, the scmode code is under supercollider/editors/scel. The contents of the sc directory needs to be copied to the Extensions directory of your application support directory. You can discover the location of your application support directory by evaluating this in the SuperCollider interpreter:

Platform.userAppSupportDir;

On OSX that will be ~/Library/Application Support/SuperCollider, so copy scel to ~/Library/Application Support/SuperCollider/Extensions, creating the directory if it doesn’t already exist.

git clone https://github.com/supercollider/supercollider.git
cd supercollider/editors/scel
sudo mkdir ~/Library/Application\ Support/SuperCollider/Extensions
sudo cp -r sc ~/Library/Application\ Support/SuperCollider/Extensions

You need to add the contents of supercollider/editors/scel/el to your load path. You can just point to where you cloned the git repo, but I prefer to have my emacs lisp in a logical place. I copied the el directory to ~/.emacs.d/vendor/scel/el directory.

Add this to your init file:

(add-to-list 'load-path "~/.emacs.d/vendor/scel/el")
(require 'sclang)

You need to set your path within Emacs also. The sclang program is located in /Applications/SuperCollider/SuperCollider.app/Contents/Resources, so that needs to be in your path.
(For hacking in iTerm, I’ve also added it to my PATH in my bash startup file).

(setenv "PATH" (concat (getenv "PATH") ":/Applications/SuperCollider:/Applications/SuperCollider/SuperCollider.app/Contents/Resources"))
(setq exec-path (append exec-path '("/Applications/SuperCollider"  "/Applications/SuperCollider/SuperCollider.app/Contents/Resources" )))

Launch Emacs. When the dust settles, type

M-x sclang-start

The interpreter will start in a buffer named SCLang:PostBuffer and you will be presented with a new buffer named SCLang:Workspace.

Start the server in the workspace.

s = Server.local;
s.boot;

There will be a SCLang menu and you can execute commands from there. You’ll see the C-x C-f will evaluate the entire document. (Or type C-c C-p b to boot. Of course you can type C-h m to get the help for sclang mode).

Create a simple noisemaker.

{[SinOsc.ar(440, 0, 0.2), SinOsc.ar(442, 0, 0.2)]}.play;

C-c C-s will stop playing.

Have fun!

Posted in Computer Music, Emacs, SuperCollider | Tagged , | Leave a comment

JDK 8, OSX, and Netbeans

Have you installed an Early Access Release of JDK 8? You may want to still use JDK 7 for some projects. I have a JavaFX 2 project that needs JDK 7 since there will be API changes in JDK8.

I’ve previously covered using non Apple JDKs on Mountain Lion. Unfortunately, that doesn’t affect NetBeans default JDK.

When you look at the JDKs in NetBeans, you will see one is the “default”. There is no setting to change it to another JDK. So, if you just installed JDK 8, then that is your default now. You can go to individual project and set which JDK you need for that project. But if you want to set the default JDK, you have to change a NetBeans config file.

Edit this file:

/Applications/NetBeans/NetBeans 7.3.app/Contents/Resources/NetBeans/etc/netbeans.conf

You want to change the key netbeans_jdkhome like this:

netbeans_jdkhome=(/usr/libexec/java_home -v 1.7)

Nope, doesn’t work. That’s a shell command and we’re in just a properties file. So, this doesn’t work either:

netbeans_jdkhome=$JAVA_HOME

You need to be explicit.

netbeans_jdkhome="/Library/Java/JavaVirtualMachines/jdk1.7.0_17.jdk/Contents/Home"

You can still get the fully qualified path to a particular version of a JDK by typing this in a shell:

/usr/libexec/java_home -v 1.7
Posted in Java, NetBeans | Tagged , , | Leave a comment

JDK 7 and JavaFX 2

Some good news about JavaFX. It is now bundled with the JDK so you don’t need to download a separate installer.

Some bad news: the runtime is not on the classpath. (In Java 8, it will be on the classpath).

A simple fix is to do this:

cd /Library/Java/JavaVirtualMachines/jdk1.7.0_17.jdk/Contents/Home/jre/lib
sudo cp jfxrt.jar ext
Posted in JavaFX | Tagged | Leave a comment

JDK 7 on OSX

You can download a dmg from Oracle’s download page. You might expect that you’d run the installer and you’d be ready to go. Not me.

I had previous versions of Java installed. In my startup file (.bash_profile) I had set this:

export JAVA_HOME=/Library/Java/Home/

With this set, java -version still reported 1.6.

I set it to this and things worked.

export JAVA_HOME=/Library/Java/JavaVirtualMachines/jdk1.7.0_17.jdk/Contents/Home

That’s a bit clunky. There is a better way.

export JAVA_HOME=$(/usr/libexec/java_home)

If you want to use another version, e.g. 1.6

export JAVA_HOME=$(/usr/libexec/java_home -v 1.6)
Posted in Java | Tagged , | 1 Response

jQuery UI 1.10 tabs

The release notes for jQuery UI 1.10.0 lists the API changes.

To handle a tab selection, you previously had to use the tab index and add a handler on the select event.

The select event is gone now. You use activate in the new API.

The index is gone too. Now you use the newPanel attribute on the tab parameter in your activate handler. You can use the newPanel’s selector attribute to determine which tabs was just selected. Oops. I mean activated.

function handleTabSelect(event, tab) {
         if (tab.newPanel.selector === "#tabone") {
...

Play with it at JSBin:

JS Bin

Here is a Gist if you prefer that.

Posted in Javascript, jQuery, jQuery UI | 1 Response

Spring @MVC configuration without XML

XML is no longer hip. Actually, there is nothing as unhip as last year’s hip. That is until it becomes hip again 30 years later in failed irony.

Honestly, if you’ve been programming Java EE since the 90s, you know full well how error prone XML config files can be. A glance at an EJB XML config file – especially the CMP relationship config from version 2 – would make any sane person run screaming into the night. Tools were supposed to help, but they really didn’t.

Even the Spring Framework, a music major’s wildly successful solution to Java EE’s problems, has been inundated by XML config files. Until now.

Let’s see how we can configure a Spring @MVC web app with no XML.

Java EE update

Starting with the Servlet 3.0 specification, we can do away with the venerable WEB-INF/web.xml configuration file. In order to do this you must do the following:

  1. Write a class that implements javax.servlet.ServletContainerInitializer
  2. Create a file named META-INF/services/javax.servlet.ServletContainerInitializer which contains the fully qualified name of your implementation.

Spring

Beginning with release 3.1, Spring provides an implementation of the ServletContainerInitializer interface aptly named SpringServletContainerInitializer. Take a peek inside spring-web-[version 3.1 and above].jar and you’ll see the META-INF/services file mentioned above.

The SpringServletContainerInitializer class delegates to an implementation of
org.springframework.web.WebApplicationInitializer that you provide. There is just one method that you need to implement: WebApplicationInitializer#onStartup(ServletContext). You are handed the ServletContext that you need to initialize.

1
2
3
4
5
6
7
8
9
10
11
12
13
public class WebAppInitializer implements WebApplicationInitializer {
 
    @Override
    public void onStartup(ServletContext servletContext) {
      WebApplicationContext appContext = set up the context
 
      ServletRegistration.Dynamic dispatcher =
        servletContext.addServlet("dispatcher", new DispatcherServlet(appContext));
      dispatcher.setLoadOnStartup(1);
      dispatcher.addMapping("/");
    }
 
 }

Setting up the app contexts

Since we are avoiding writing XML config files, I suggest that instead of using the XmlWebApplicationContext class, use the AnnotationConfigWebApplicationContext which supports classpath scanning for Spring annotation based configuration. You can explicitly add configuration classes, or have the context scan for them.

To start up and shut down the context, we add a ContextLoaderListener.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
@Override
public void onStartup(ServletContext servletContext) throws ServletException {
 
   // Create the root appcontext
   AnnotationConfigWebApplicationContext rootContext = new AnnotationConfigWebApplicationContext();
   rootContext.register(RootConfig.class);
   // since we registered RootConfig instead of passing it to the constructor
   rootContext.refresh(); 
 
   // Manage the lifecycle of the root appcontext
   servletContext.addListener(new ContextLoaderListener(rootContext));
   servletContext.setInitParameter("defaultHtmlEscape", "true");
 
   // now the config for the Dispatcher servlet
   AnnotationConfigWebApplicationContext mvcContext = new AnnotationConfigWebApplicationContext();
   mvcContext.register(WebMvcConfig.class);
 
   // The main Spring MVC servlet.
   ServletRegistration.Dynamic appServlet = servletContext.addServlet(
      "appServlet", new DispatcherServlet(mvcContext));
		appServlet.setLoadOnStartup(1);
   Set<String> mappingConflicts = appServlet.addMapping("/");
 
   if (!mappingConflicts.isEmpty()) {
      for (String s : mappingConflicts) {
         logger.error("Mapping conflict: " + s);
      }
      throw new IllegalStateException(
         "'appServlet' cannot be mapped to '/' under Tomcat versions <= 7.0.14");
   }
}

Java config files

The RootConfig Java class we specified in the previous example needs to use the @Configuration annotation. Essentially this class corresponds to the <beans> element in Spring XML config files. The <bean> elements are now methods with the @Bean annotation that return a bean instance. The <context:component-scan> element is now the class level annotation @ComponentScan.

1
2
3
4
5
6
7
8
9
10
@Configuration
@ComponentScan(basePackages = { "com.rockhoppertech.mvc.service",
		"com.rockhoppertech.mvc.repositories.internal" })
public class RootConfig {
 
   @Bean public SomeClass someClass() { 
      return someInstance;
   }
 
}

Filters

You can also create any Servlet Filters here. Here are a few Spring provided Filters.

In this example I set up the CharacterEncodingFilter.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
 
@Override
public void onStartup(ServletContext servletContext) throws ServletException {
 
...
 
   FilterRegistration.Dynamic fr = servletContext.addFilter("encodingFilter",  
      new CharacterEncodingFilter());
   fr.setInitParameter("encoding", "UTF-8");
   fr.setInitParameter("forceEncoding", "true");
   fr.addMappingForUrlPatterns(null, true, "/*");
...
 
}

@Enable*

XML namespaces are replaced by class level annotations that begin with @Enable.

  • @EnableWebMvc
  • @EnableAsync
  • @EnableScheduling
  • @EnableLoadTimeWeaving
  • @EnableTransactionManagement

Right now we configure MVC using the mvc: XML namespace, for example: <mvc:annotation:driven/>. The @EnableWebMvc annotation is the replacement.

Spring MVC configuration

Write a configuration class that contains the @EnableWebMvc annotation, which is defined by DelegatingWebMvcConfiguration. In addition, you will probably do a component scan for controllers here.

To customize the defaults, implement WebMvcConfigurer or extend WebMvcConfigurerAdapter. Any overridden method that does not return NULL will use that value instead of the default.

1
2
3
4
5
6
7
8
@Configuration
@EnableWebMvc
// scan for controllers
@ComponentScan(basePackages = { "com.rockhoppertech.mvc.web" })
public class WebMvcConfig extends WebMvcConfigurerAdapter {
   @Bean ViewResolver viewResolver() { ... }
   @Bean MessageSource messageSource() { ... }
etc.

Resources

Posted in Spring MVC | Tagged , | 5 Responses

Eclipse JavaScript Libraries Validation

I use several JavaScript libraries in my web apps. Just one of the many reasons I like Spring MVC over other web frameworks – like JSF – is that you can easily use current web technologies. HTML5? CSS3? jQuery? No problem!

Actually there is one problem, but it’s an Eclipse (or Spring STS) problem. When you add JavaScript to your project, Eclipse will validate the code. This is what I want when I am developing code! But I don’t want to debug or modify third party code. The Eclipse validator will almost always complain about minified versions of JavaScript files.

Project with JavaScript validation errors

Click to Enlarge

You can turn off validation. Problem solved. You can also remove squirrels from your backyard bird feeders with a bazooka.

A better solution

  • Open the project’s properties dialog. (right click, properties or Command-I)
  • Select JavaScript then Include Path from the tree on the left.
  • Select the Source tab in the property sheet.
  • Select the Excluded node from the project tree.
  • Press the Edit button.
  • In the modal dialog that pops up, add an Exclusion Pattern.

I put third party JavaScript in src/main/webapp/resources/js/libs and my own JavaScript in src/main/webapp/resources/js. So I add only src/main/webapp/resources/js/libs as an exclusion pattern. Then I no longer see jQuery, modernizr, or bootstrap errors, but I do see JavaScript errors in the code that I’m developing.

Eclipse JavaScript Validation

Click to Enlarge

Posted in Eclipse, jQuery, Spring MVC | Tagged , | Leave a comment

iOS Audio Unit Graph

Apple’s Core Audio API is very powerful. It is also not easy. With Great Power comes Great Heartburn when the documentation is out of date and/or scattered. Sometimes the needs of the few outweigh the needs of the many as in the case of conference talks. At WWDC 2011, session 411 was named “Music in iOS and Lion” (see the Resources). If you watch the presentation, you wil get a taste of what is possible. Unfortunately they were as stingy with code examples as Webern was with notes. (The goal of conference presentations is to show off, and not to educate the viewers. Not picking on these guys, but this has been true of every presentation I’ve ever seen).

So, here is a first step. It is a complete project that runs and produces sound. Many things are missing – on purpose – which will be added in subsequent blog posts. The point is to get the main point across in the simplest manner possible.

Core Audio is a C API. Currently there are no official Objective-C bindings. So, you need to mix your Objective-C code with C code. In more advanced code you will be using many Core Foundation types too, so get comfortable with this intermixing. You will also use classes such as AUGraph from the AudioToolbox framework. So, in your project setup, (under build phases in XCode 4+) you need to link to the AudioToolbox framework and the CoreAudio framework. You do not link to the AudioUnit framework here. If you do, you will get an error.

Most CA (Core Audio) API functions return an OSStatus value. This is a 32 bit integer.

OSStatus NewAUGraph ( AUGraph *outGraph );

The function to set up your graph is NewAUGraph. Here is one way to handle the result.

1
2
3
4
5
6
AUGraph outGraph;
OSStatus result = noErr;
result = NewAUGraph ( &outGraph );
if(result != noErr) {
   // handle it
}

Chris Adamson (see the Resources) wrote a function that many programmers use to display the status as a string if possible. If not he prints the digit. This is a bit like the old original Mac toaster that would pop up a dialog stating “Error -151 Occurred”. So, I’ve added a switch statement that checks the value against well known error constants.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
void CheckError(OSStatus error, const char *operation) {
	if (error == noErr) return;
 
	char str[20];
	// see if it appears to be a 4-char-code
	*(UInt32 *)(str + 1) = CFSwapInt32HostToBig(error);
	if (isprint(str[1]) && isprint(str[2]) && isprint(str[3]) && isprint(str[4])) {
		str[0] = str[5] = ''';
		str[6] = '';
	} else {
		// no, format it as an integer
		sprintf(str, "%d", (int)error);
	}
	fprintf(stderr, "Error: %s (%s)n", operation, str);
 
      switch(error) {
        case kAUGraphErr_NodeNotFound:
many more...

Using this utility function, your code will look like this instead.

CheckError(NewAUGraph(&outGraph),"NewAUGraph");

You will find some examples on the net that mix CA code into their View Controllers. I’d rather not. So, I created a regular class named GDSoundEngine that will contain all of the CA and Audio Unit frobs. The View Controller will simply tell the SoundEngine to do the things specified in its public interface, such as playNoteOn:velocity.

For example, this is how the view controller interacts with the sound engine. The note value is from the tag set on the UIButton from the storyboard.

- (IBAction)noteOn:(UIButton *)sender {
    UInt32 velocity = 100;
    [self.soundEngine playNoteOn:[sender tag] :velocity ];
}
- (IBAction)noteOff:(UIButton *)sender {
    [self.soundEngine playNoteOff:[sender tag] ];
}

Here is the setup in the View Controller.

1
2
3
4
5
6
7
8
9
10
11
12
13
@interface GDViewController ()
@property (strong) id soundEngine;
@end
 
@implementation GDViewController
@synthesize soundEngine = _soundEngine;
 
- (void)viewDidLoad
{
    [super viewDidLoad];
    self.soundEngine = [[GDSoundEngine alloc] init];
}
etc.

The SoundEngine has a private interface with properties for the audio guts.

1
2
3
4
5
6
7
@interface GDSoundEngine()
@property (readwrite) AUGraph processingGraph;
@property (readwrite) AUNode samplerNode;
@property (readwrite) AUNode ioNode;
@property (readwrite) AudioUnit samplerUnit;
@property (readwrite) AudioUnit ioUnit;
@end

Notice that for each AUNode there is a corresponding AudioUnit. We will step through the details for each of these now.

Build an Audio Processing Graph

Here are the steps to build an Audio Unit Graph.

  1. Instantiate an AUGraph with the function NewAUGraph.
  2. Instantiate one or more AUNodes, each of which represents one audio unit in the graph. To create and add the nodes to the graph, use the function AUGraphAddNode.
    You first create an AudioComponentDescription to pass into this function.
  3. Open the graph and instantiate the audio units with the function AUGraphOpen.
  4. Obtain references to the audio units with the function AUGraphNodeInfo.

Step 1. The AUGraph is a property named processingGraph. In the implementation I use the underscore pattern for the instance variable. Then in the graph init method, the graph is created.

1
2
3
4
5
6
7
@synthesize processingGraph = _processingGraph;
...
- (BOOL) createAUGraph
{
    CheckError(NewAUGraph(&_processingGraph),
			   "NewAUGraph");
...

Here is an example of step 2. The AudioComponentDescription is set up first. The componentSubType field here is set to kAudioUnitSubType_Sampler. This AU was added in iOS 5. Then the AUNode is instantiated and added to the graph with AUGraphAddNode. Note that _samplerNode is the property’s instance variable.

1
2
3
4
5
6
7
8
9
    // create the sampler
    // for now, just have it play the default sine tone
    AudioComponentDescription cd;
    cd.componentType = kAudioUnitType_MusicDevice;
    cd.componentSubType = kAudioUnitSubType_Sampler;
    cd.componentManufacturer = kAudioUnitManufacturer_Apple;
    cd.componentFlags = 0;
    cd.componentFlagsMask = 0;
    CheckError(AUGraphAddNode(self.processingGraph, &cd, &_samplerNode), "AUGraphAddNode");

The RemoteIO AU is created in the same manner.

1
2
3
4
5
6
7
8
    // I/O unit
    AudioComponentDescription iOUnitDescription;
    iOUnitDescription.componentType          = kAudioUnitType_Output;
    iOUnitDescription.componentSubType       = kAudioUnitSubType_RemoteIO;
    iOUnitDescription.componentManufacturer  = kAudioUnitManufacturer_Apple;
    iOUnitDescription.componentFlags         = 0;
    iOUnitDescription.componentFlagsMask     = 0;
    CheckError(AUGraphAddNode(self.processingGraph, &iOUnitDescription, &_ioNode), "AUGraphAddNode");

Now in step 3 we can instantiate the Audio Units from the AUNodes. The AUGraph needs to be open via AUGraphOpen for this to work.

1
2
3
4
5
6
7
8
    // The graph needs to be open before you call AUGraphNodeInfo
   CheckError(AUGraphOpen(self.processingGraph), "AUGraphOpen");
 
   CheckError(AUGraphNodeInfo(self.processingGraph, self.samplerNode, NULL, &_samplerUnit), 
               "AUGraphNodeInfo");
 
   CheckError(AUGraphNodeInfo(self.processingGraph, self.ioNode, NULL, &_ioUnit), 
               "AUGraphNodeInfo");

In step 4, we wire the Audio Units together with AUGraphConnectNodeInput.

1
2
3
4
5
6
7
 
    AudioUnitElement ioUnitOutputElement = 0;
    AudioUnitElement samplerOutputElement = 0;
    CheckError(AUGraphConnectNodeInput(self.processingGraph, 
                                       self.samplerNode, samplerOutputElement, // srcnode, inSourceOutputNumber
                                       self.ioNode, ioUnitOutputElement), // destnode, inDestInputNumber
               "AUGraphConnectNodeInput");

For debugging there is a function named CAShow that will display the current state of audio objects.

1
CAShow(self.processingGraph);

The completed graph setup method

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
- (BOOL) createAUGraph
{
    NSLog(@"Creating the graph");
 
    CheckError(NewAUGraph(&_processingGraph),
			   "NewAUGraph");
 
    // create the sampler
    // for now, just have it play the default sine tone
    AudioComponentDescription cd;
    cd.componentType = kAudioUnitType_MusicDevice;
    cd.componentSubType = kAudioUnitSubType_Sampler;
    cd.componentManufacturer = kAudioUnitManufacturer_Apple;
    cd.componentFlags = 0;
    cd.componentFlagsMask = 0;
    CheckError(AUGraphAddNode(self.processingGraph, &cd, &_samplerNode), "AUGraphAddNode");
 
 
    // I/O unit
    AudioComponentDescription iOUnitDescription;
    iOUnitDescription.componentType          = kAudioUnitType_Output;
    iOUnitDescription.componentSubType       = kAudioUnitSubType_RemoteIO;
    iOUnitDescription.componentManufacturer  = kAudioUnitManufacturer_Apple;
    iOUnitDescription.componentFlags         = 0;
    iOUnitDescription.componentFlagsMask     = 0;
    CheckError(AUGraphAddNode(self.processingGraph, &iOUnitDescription, &_ioNode), "AUGraphAddNode");
 
    // now do the wiring. The graph needs to be open before you call AUGraphNodeInfo
	CheckError(AUGraphOpen(self.processingGraph), "AUGraphOpen");
 
	CheckError(AUGraphNodeInfo(self.processingGraph, self.samplerNode, NULL, &_samplerUnit), 
               "AUGraphNodeInfo");
 
    CheckError(AUGraphNodeInfo(self.processingGraph, self.ioNode, NULL, &_ioUnit), 
               "AUGraphNodeInfo");
 
    AudioUnitElement ioUnitOutputElement = 0;
    AudioUnitElement samplerOutputElement = 0;
    CheckError(AUGraphConnectNodeInput(self.processingGraph, 
                                       self.samplerNode, samplerOutputElement, // srcnode, inSourceOutputNumber
                                       self.ioNode, ioUnitOutputElement), // destnode, inDestInputNumber
               "AUGraphConnectNodeInput");
 
 
	NSLog (@"AUGraph is configured");
	CAShow(self.processingGraph);
 
    return YES;
}

Starting the graph

Once the graph has been setup, you are ready to start processing by calling the function AUGraphInitialize. This calls the AudioUnitInitialize function of each Audio Unit in the graph. It also validates the graph’s connections and audio data stream formats. Finally, the graph is started with AUGraphStart.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
- (void) startGraph
{
    if (self.processingGraph) {
        Boolean outIsInitialized;
        CheckError(AUGraphIsInitialized(self.processingGraph,
                                        &outIsInitialized), "AUGraphIsInitialized");
        if(!outIsInitialized)
            CheckError(AUGraphInitialize(self.processingGraph), "AUGraphInitialize");
 
        Boolean isRunning;
        CheckError(AUGraphIsRunning(self.processingGraph,
                                    &isRunning), "AUGraphIsRunning");
        if(!isRunning)
            CheckError(AUGraphStart(self.processingGraph), "AUGraphStart");
        self.playing = YES;
    }
}

Sending a note to the sampler

The final task is to define the public methods that are called by the View Controller. Here, the function MusicDeviceMIDIEvent sends a MIDI message to the sampler audio unit. For example, 0×90 is the MIDI note on message for channel 0.

1
2
3
4
5
6
7
8
9
10
11
12
- (void)playNoteOn:(UInt32)noteNum :(UInt32)velocity 
{
    UInt32 noteCommand = 0x90;
    NSLog(@"playNoteOn %lu %lu cmd %lx", noteNum, velocity, noteCommand);
    CheckError(MusicDeviceMIDIEvent(self.samplerUnit, noteCommand, noteNum, velocity, 0), "NoteOn");
}
 
- (void)playNoteOff:(UInt32)noteNum
{
    UInt32 noteCommand = 0x80;
    CheckError(MusicDeviceMIDIEvent(self.samplerUnit, noteCommand, noteNum, 0, 0), "NoteOff");
}

Summary

That’s it for now. The sound that you will hear is a beautiful sine wave. In the next blog post, I will show you how to set up the sample unit to use sounds from a DLS file or a SountFont2 file.

Resources

Posted in iOS | Tagged , | 2 Responses