OpenEars upgrade guide

How to upgrade from OpenEars 2.0x to OpenEars 2.5

All that is necessary is to replace any OpenEars platform frameworks and bundles using the new 2.5 versions, including plugins and voices such as Flite or NeatSpeech voices, and then to add any new language (or alternate English version) acoustic models you want to use from the language download page.

How to upgrade from OpenEars 1.x to OpenEars 2.x

There have been changes to the OpenEars API, assets, and class naming in version 2.0 in order to support its new capabilities. You can read more about how and why here. Because of that, the first time you upgrade from OpenEars 1.x to 2.x, you need to undertake some additional steps:

Step 0: Commit your current version to your versioning system, or create a backup if you don’t use a versioning system (please start using a versioning system). That way, if things go wrong in this process, you know you have a clean version to start over with. Make a cup of tea, put on some nice music, and relax and take your time with the following process. You only have to do it once, and the more careful and deliberate you are, the less likely it is that you’ll have any problems. Remember that if you have trouble, you can get help at the forums. Here we go:

Step 1: Remove all OpenEars 1.x frameworks and assets. OpenEars 2.0 includes new acoustic models and re-compiled voices and plugin frameworks, and 2.0 is not backwards-compatible with the old ones, so if you keep them around you will get poor results even if the app builds (which is unlikely).

After making a note of where the 1.x frameworks and assets were so you can easily install the new versions without having to make changes in your app build settings, remove and delete any of the following that you have added to your project (if you want to keep a backup of your old frameworks, which isn’t a bad idea since the old versions will not remain available on the site, zip the backup so there is no risk of being confused by a build setting unexpectedly linking to an outdated framework):

OpenEars.framework
Slt.framework
AcousticModelEnglish.bundle
AcousticModelSpanish.bundle
RapidEars.framework or RapidEarsDemo.framework
Rejecto.framework or RejectoDemo.framework
RuleORama.framework or RuleORamaDemo.framework
SaveThatWave.framework or SaveThatWaveDemo.framework
NeatSpeech.framework or NeatSpeechDemo.framework,

and the following NeatSpeech voice data if you are a NeatSpeech user:

Beatrice.framework
Daniel.framework
Elliott.framework
Emma.framework
EmmaAlternate.framework
Martina.framework
Mateo.framework
NeatSpeechRequired.bundle
RequiredSpanishData.bundle
RequiredUKEnglishData.bundle
RequiredUSEnglishData.bundle
Valeria.framework
William.framework
WilliamAlternate.framework

After removing any of these items in your app’s Project Navigator (leave your build settings pane alone – if all goes well we won’t ever need to make changes there), search for these elements in your file system using Spotlight and make sure they don’t turn up anywhere that they can be linked to. Confirm that your app can’t compile due to the absence of these elements. Once you are satisfied that your app cannot link to outdated assets or frameworks any more, quit Xcode and move on to step 2.

Step 2: Re-install the 2.x versions of the assets in the same locations as the old ones. Download the new OpenEars, and you can get any licensed plugins at the licensed plugin server (2.o is a free upgrade for licensed users), and you can get the demos at the following pages:

https://www.politepix.com/neatspeech
https://www.politepix.com/rapidears
https://www.politepix.com/rejecto
https://www.politepix.com/ruleorama
https://www.politepix.com/savethatwave

If you’re  a licensed user you will see the 1.x versions of your licensed frameworks still available at the licensed download server, so make sure to only download the 2.x versions for this upgrade. Place the new frameworks and assets in the same location as the old ones were. Reopen your project and verify in Xcode that the 2.0 frameworks and assets are now seen by your project (for instance, make sure they are shown in black in the Project Navigator, not red). Don’t attempt to build yet; you will get errors. When your project doesn’t show any missing OpenEars Platform assets in the Project Navigator, move on to step 3:

Step 3: Update your import statements. Make the following changes to any of these #import statements which are present in your app (if you use #include instead of #import, just substitute it for ‘import’ in the following):

REPLACE #import <OpenEars/LanguageModelGenerator.h> WITH #import <OpenEars/OELanguageModelGenerator.h>
REPLACE #import <OpenEars/FliteController.h> WITH #import <OpenEars/OEFliteController.h>
REPLACE #import <OpenEars/PocketsphinxController.h> WITH #import <OpenEars/OEPocketsphinxController.h>
REPLACE #import <OpenEars/OpenEarsEventsObserver.h> WITH #import <OpenEars/OEEventsObserver.h>
REPLACE #import <OpenEars/OpenEarsLogging.h> WITH #import <OpenEars/OELogging.h>
REPLACE #import <OpenEars/AcousticModel.h> WITH #import <OpenEars/OEAcousticModel.h>

It is pretty unlikely that you have any direct references to #import <OpenEars/FliteVoice.h> in your app, but if you somehow do, please replace those imports with #import <OpenEars/OEFliteVoice.h> instead.

If you use the NeatSpeech demo:

REPLACE #import <NeatSpeechDemo/FliteController+NeatSpeech.h> WITH #import <NeatSpeechDemo/OEFliteController+NeatSpeech.h>

If you use licensed Neatspeech:

REPLACE #import <NeatSpeech/FliteController+NeatSpeech.h> WITH #import <NeatSpeech/OEFliteController+NeatSpeech.h>

If you use the RapidEars demo:

REPLACE #import <RapidEarsDemo/PocketsphinxController+RapidEars.h> WITH #import <RapidEarsDemo/OEPocketsphinxController+RapidEars.h>
REPLACE #import <RapidEarsDemo/OpenEarsEventsObserver+RapidEars.h> WITH #import <RapidEarsDemo/OEEventsObserver+RapidEars.h>

If you use licensed RapidEars:

REPLACE #import <RapidEars/PocketsphinxController+RapidEars.h> WITH #import <RapidEars/OEPocketsphinxController+RapidEars.h>
REPLACE #import <RapidEars/OpenEarsEventsObserver+RapidEars.h> WITH #import <RapidEars/OEEventsObserver+RapidEars.h>

If you use the Rejecto demo:

REPLACE #import <RejectoDemo/LanguageModelGenerator+Rejecto.h> WITH #import <RejectoDemo/OELanguageModelGenerator+Rejecto.h>

If you use licensed Rejecto:

REPLACE #import <Rejecto/LanguageModelGenerator+Rejecto.h> WITH #import <Rejecto/OELanguageModelGenerator+Rejecto.h>

If you use the RuleORama demo:

REPLACE #import <RuleORamaDemo/LanguageModelGenerator+RuleORama.h> WITH #import <RuleORamaDemo/OELanguageModelGenerator+RuleORama.h>

If you use licensed RuleORama:

REPLACE #import <RuleORama/LanguageModelGenerator+RuleORama.h> WITH #import <RuleORama/OELanguageModelGenerator+RuleORama.h>

If you use the SaveThatWave demo:

REPLACE #import <SaveThatWaveDemo/OpenEarsEventsObserver+SaveThatWave.h> WITH #import <SaveThatWaveDemo/OEEventsObserver+SaveThatWave.h>

If you use licensed SaveThatWave:

REPLACE #import <SaveThatWave/OpenEarsEventsObserver+SaveThatWave.h> WITH #import <SaveThatWave/OEEventsObserver+SaveThatWave.h>

Great. You still can’t compile yet, but if these are all replaced in your app, you can move on to step 4.

Step 4: Update class names. As you probably guessed, since the headers now have different names, the classes with the renamed headers also have different names, so they need to be referred to correctly in your app. Before making the following changes, make absolutely sure that you do case-sensitive searching so that you only change the class names (which are declared beginning with a capital letter), not your variable names (which I hope was not declared with a capital letter!). Absolutely do not run these searches on the OpenEars source code, just your app:

REPLACE REFERENCES TO THE CLASS LanguageModelGenerator WITH OELanguageModelGenerator
REPLACE REFERENCES TO THE CLASS FliteController WITH OEFliteController
REPLACE REFERENCES TO THE CLASS PocketsphinxController WITH OEPocketsphinxController
REPLACE REFERENCES TO THE CLASS OpenEarsEventsObserver WITH OEEventsObserver
REPLACE REFERENCES TO THE CLASS OpenEarsEventsObserverDelegate WITH OEEventsObserverDelegate (this is usually found in your headers for view controllers using OpenEarsEventsObserver)
REPLACE REFERENCES TO THE CLASS OpenEarsLogging WITH OELogging
REPLACE REFERENCES TO THE CLASS AcousticModel WITH OEAcousticModel

It is pretty unlikely that you have any direct references to FliteVoice in your app, but if you somehow do, please replace those class references with OEFliteVoice instead.

Super, you are now ready to move on to the final step 5.

Step 5: architectural changes.

Since OEPocketsphinxController is now a singleton, it is no longer necessary to use lazy instantiation to ensure that there is only one instance of it. If you had previously used the recommended lazy instantiation method, the easiest thing to do is to change all of those accessors which look like this:

- (PocketsphinxController *)pocketsphinxController {
    if (pocketsphinxController == nil) {
        pocketsphinxController = [[PocketsphinxController alloc] init];
    }
    return pocketsphinxController;
}

To look like this:

- (OEPocketsphinxController *)pocketsphinxController {
    if (pocketsphinxController == nil) {
        pocketsphinxController = [OEPocketsphinxController sharedInstance];
    }
    return pocketsphinxController;
}

But you can also just replace your calls to self.pocketsphinxController to calls to [OEPocketsphinxController sharedInstance] and get rid of the accessors altogether – that is preferable for reasons of clarity, but the approach above requires changes in the fewest places, so it’s your call.

Another major architectural change is that OELanguageModelGenerator no longer returns an NSDictionary containing the paths to your language model/grammar and dictionary if it is successful. It now returns nil if there was no error, or an error if there was an error. It now has convenience methods to get the correct paths to your successfully-generated language models, grammars and dictionaries if generation does not contain an error. Therefore, instead of doing this to generate:

LanguageModelGenerator *languageModelGenerator = [[LanguageModelGenerator alloc] init];
NSError *error = [languageModelGenerator generateLanguageModelFromArray:firstLanguageArray withFilesNamed:@"FirstOpenEarsDynamicLanguageModel" forAcousticModelAtPath:[AcousticModel pathToModel:@"AcousticModelEnglish"]]; // Change "AcousticModelEnglish" to "AcousticModelSpanish" in order to create a language model for Spanish recognition instead of English.
NSDictionary *firstDynamicLanguageGenerationResultsDictionary = nil;

if([error code] != noErr) {
    NSLog(@"Dynamic language generator reported error %@", [error description]);
} else {
    firstDynamicLanguageGenerationResultsDictionary = [error userInfo];
    NSString *lmFile = [firstDynamicLanguageGenerationResultsDictionary objectForKey:@"LMFile"];
    NSString *dictionaryFile = [firstDynamicLanguageGenerationResultsDictionary objectForKey:@"DictionaryFile"];
    NSString *lmPath = [firstDynamicLanguageGenerationResultsDictionary objectForKey:@"LMPath"];
    NSString *dictionaryPath = [firstDynamicLanguageGenerationResultsDictionary objectForKey:@"DictionaryPath"];
}

You do this instead:

OELanguageModelGenerator *languageModelGenerator = [[OELanguageModelGenerator alloc] init];
NSError *error = [languageModelGenerator generateLanguageModelFromArray:firstLanguageArray withFilesNamed:@"MyModelName" forAcousticModelAtPath:[OEAcousticModel pathToModel:@"AcousticModelEnglish"]];
if(error != noErr) {
    NSLog(@"Dynamic language generator reported error %@", [error description]);
} else {
    NSString *dictionaryPath = [_languageModelGenerator pathToSuccessfullyGeneratedDictionaryWithRequestedName:@"MyModelName"];
    NSString *lmPath = [_languageModelGenerator pathToSuccessfullyGeneratedLanguageModelWithRequestedName:@"MyModelName"];
}

Please keep in mind that the following classes, since they are asynchronous and event-driven, have to be instantiated as properties of your class rather than as method-scope variables:

OEEventsObserver
OEFliteController
SaveThatWaveController

It is almost certain that you already have them as class properties of your view controller if you are using them, but now they are only being tested as properties so there is no support for using them in other scopes, and if you see unexpected behavior with these classes, check and make sure they are properties. It is no longer necessary to instantiate any of the objects using the lazy accessor method – it is fine (and preferable) to instantiate them in the usual way.

OEPocketsphinxController does not use calibration anymore, so OEEventsObserver no longer uses the calibration callbacks. It has some more informative error state callbacks, so take a look at its header to get up-to-date with its changes.

Checking for API changes

The last concern regarding architecture is to take a look at the headers whose API you are using and check for API changes. The two mentioned above are the most dramatic API changes, but there are a few smaller ones which don’t affect all users, for example SaveThatWave no longer has a method called “startForRapidEars” because its method “start” now just works with RapidEars, and RapidEars no longer has methods setFasterFinals: or SetFasterPartials: or setRapidEarsAccuracy: because it has been so optimized that they are no longer needed. The former delegate methods – (void) rapidEarsDidDetectBeginningOfSpeech and – (void) rapidEarsDidDetectEndOfSpeech are now handled in OEEventsObserver’s callbacks – (void) pocketsphinxDidDetectSpeech and – (void) pocketsphinxDidDetectFinishedSpeech. The only OEEventsObserver callbacks now handled by OEEventsObserver+RapidEars are the ones which are unique to RapidEars, i.e. its hypothesis methods.

OEFliteController no longer has the properties noAudioSessionOverrides, audioSessionHasBeenInstantiated, and quietFlite because they are no longer needed.

Another API change is that before you start setting properties of OEPocketsphinxController for the first time, it is necessary to call its setActive method:

[[OEPocketsphinxController sharedInstance] setActive:TRUE error:nil];

As mentioned in the previous section, there are API changes in OEEventsObserver so you will want to remove calibration callbacks and other callbacks which are not longer part of the protocol, and add the more-informative listening setup error callbacks.

Compiling

Once you have checked for API changes and verified that you are using the current API (most likely if you have made the changes mentioned above, you’re fine), you should be able to compile.

Noise sensitivity

If you are finding OEPocketsphinxController more sensitive to noise than it used to be, or alternately if you are finding that it excludes real speech as if it were noise, you can change the OEPocketsphinxController property vadThreshold. In OpenEars it defaults to 2.0 (but this default could change as I get feedback from you about the settings you’re using) and making it higher will exclude more noise (with the possibility of excluding some intended speech if it is too high), and making it lower will reduce false positive exclusion of speech (with the possibility of allowing in too much noise if it is too low). Setting it lower than 1.5, especially in combination with a shortened secondsOfSilenceToDetect, can cause lags in hypotheses being returned since it can end up trying to analyze all sound. CMU recommends setting the upper limit no higher than 3.0.

Building the framework yourself

Most developers will not need to worry about this last point, but if your requirements involve doing a new build of the framework, there is a change there – in order to make a universal binary build of the framework (i.e. one that works with both devices and simulators) it is necessary to archive the framework (which will ultimately copy a universal binary into the distribution’s Frameworks folder) rather than just building it. Just selecting build will build only for devices or only for simulators depending on which is targeted. Selecting Archive will build for all supported architectures (as of the time of writing this guide, supported architectures are armv7, arm64, i386, x86_64, with armv7s no longer supported because Apple has removed it from the standard architecture list due to the low necessity of supporting it for compatibility and optimization). Additionally, keep in mind that if you do a debug build of OpenEars.framework, performance can be slower, so make sure that you test against an archive version of the framework before raising performance issues in the forums. Again, most developers don’t need to worry about this.

If the sample app doesn’t work

In the 2.0 version of OpenEars, the sample app requires that the distribution folder have its default name of OpenEarsDistribution. If you have downloaded and unzipped multiple copies and you have folders with names such as “OpenEarsDistribution 2” the sample app will not find the framework. I’ll fix this in 2.01, but for now just run the sample app from the folder with its unaltered name if you are having this issue.

Getting help if it doesn’t work

Those are the big points. If you have any difficulties, just check in at the forums and I will help you, and please be ready to copy and paste the exact errors you’ve received so I can help!