I want to get familiar with the Objective C/Interface Builder programming on mac OSX. I'm usually working with sounds, so I'm trying to code a simple audio player, using the NSSound class.
I already managed to create a basic application that loads a sound by clicking an "Importer" button, and then play it by clicking a "Lire" button.
Here is my problem : I want to have a TextField in the window that displays "Ça joue Simone !..." when the sound is played, and nothing when the sound is finished playing.
When clicking the "Lire" button, the message I want is successfully displayed, but it stays there even after the sound is finished playing.
That's why I guess I have a misunderstanding on how to use the didFinishPLaying delegate method.
Does anybody have a hint ?
//
// ControleurLecteur.h
// LecteurSon
//
#import <Cocoa/Cocoa.h>
#interface ControleurLecteur : NSObject {
NSSound *son ;
IBOutlet NSTextField *infoTextField ;
IBOutlet NSTextField *cheminField ;
IBOutlet NSTextField *durationField ;
}
-(IBAction)lireSon:(id)sender ;
-(IBAction)importerSon:(id)sender ;
-(IBAction)son:(NSSound *)son didFinishPlaying:(BOOL)bienjoue;
#end
//
// ControleurLecteur.m
// LecteurSon
//
#import "ControleurLecteur.h"
#implementation ControleurLecteur
-(IBAction)importerSon:(id)sender {
NSString *Chemin = [[NSString alloc] initWithString:(NSString *)#"epno"] ;
son = [[NSSound soundNamed:Chemin] retain];
// Limiter l'affichage de la durée à 3 chiffres après la virgule
NSNumberFormatter *numberFormatter = [[NSNumberFormatter alloc] init];
[numberFormatter setNumberStyle:NSNumberFormatterDecimalStyle];
[numberFormatter setMaximumFractionDigits:3];
[[durationField cell] setFormatter:numberFormatter];
// Remplissage du champ de texte durée
[durationField setFloatValue:[son duration]] ;
}
-(IBAction)lireSon:(id)sender {
BOOL bienjoue;
bienjoue = [son play];
bienjoue = TRUE;
[infoTextField setStringValue:#"Ça joue Simone !..."];
son:didFinishPlaying:bienjoue;
}
-(IBAction)son:(NSSound *)son didFinishPlaying:(BOOL)bienjoue {
[infoTextField setStringValue:#""] ;
}
#end
You must set the delegate of son after you create it:
son = [[NSSound soundNamed: Chemin] retain];
[son setDelegate: self];
Otherwise, son has no idea where to send its delegate messages.
You must also take care to preserve the English names of delegate messages. If you translate them into French, they won't be understood by Objective-C (the names of arguments can be anything, however.) Instead of:
-(IBAction)son:(NSSound *)son didFinishPlaying:(BOOL)bienjoue
Use:
-(void)sound:(NSSound *)son didFinishPlaying:(BOOL)bienjoue
Most developers I know opt to code entirely in English, since almost all programming languages use English-based keywords, class names, etc., and combining their native languages with English tends to just make a difficult-to-read mess.
You forgot delegate:
#interface ControleurLecteur : NSObject <Son> {
}
Related
I am very new to objective c and OpenEars so please forgive me if I have some messy code and if I am lost in very simple problem.
Anyhow, I have two controllers in this application. The first being the default ViewController and the second one being a new one that I made called ReplyManagerController.
The code in the ViewController basically uses the one in the tutorial with some (maybe more some) changes.
EDIT:
The app is supposed to be a basic app where a user says something and the app replies.
But the original problem was that I could not get the string to display or TTS to work when my ViewController got it's string from another class/controller.
The answer in my below mentions that it was probably because my other class was calling my ViewController without the self.fliteController initialized.
How would I initialize the ViewController with self.fliteController initialized?
ViewController.m
- (void) pocketsphinxDidReceiveHypothesis:(NSString *)hypothesis recognitionScore:(NSString *)recognitionScore utteranceID:(NSString *)utteranceID {
NSString *strResult = hypothesis; //speech to text to string
ReplyManager* replyObject = [[ReplyManager alloc] init];
[replyObject speechResult:(NSString*)strResult viewController:self];
}
- (void) getReply:(NSString*)replyStr{
[self.fliteController say:replyStr withVoice:self.slt];
[self updateText:replyStr];
}
- (IBAction)updateText:(NSString*)replyStr{
labelOne.text = replyStr;
labelOne.adjustsFontSizeToFitWidth = YES;
labelOne.minimumFontSize = 0;
}
Any help will be great! Thanks!
ReplyManager.m
- (void) speechResult:(NSString*)strResult {
NSString *replystr;
NSString *greetings = #"Hi!";
NSString *sorry = #"Sorry I didn't catch that?";
ViewController* getReply = [[ViewController alloc] init];
if ([strResult isEqualToString:#"HELLO"])
{
replystr = greetings;
[getReply getReply:(NSString*)replystr];
}
else
{
replystr = sorry;
[getReply getReply:(NSString*)replystr];
}
}
EDIT 2:
viewDidLoad Method
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
self.fliteController = [[OEFliteController alloc] init];
self.slt = [[Slt alloc] init];
self.openEarsEventsObserver = [[OEEventsObserver alloc] init];
[self.openEarsEventsObserver setDelegate:self];
}
ViewController* getReply = [[ViewController alloc] init];
Here you init a new ViewController which does not have self.fliteController defined most likely. You need to reuse previos controller, for example like this:
[replyObject speechResult:(NSString*)strResult viewController:self];
So you can use already initialized viewController later. Overall it is better to initialize objects like viewController or replyController beforehand, not inside callback methods.
It sounds like a timing issue where you're trying to use fliteController before it's been initialized.
In your ViewController class, where do you assign a value to the fliteController property? In an initializer? -(void)viewDidLoad?
In ReplyManagerController add:
ViewController* getReply = [[ViewController alloc] init];
// Add these lines
NSLog(getReply.fliteController); // Is this nil?
[getReply.view layoutIfNeeded];
NSLog(getReply.fliteController); // Is it still nil?
Does the above fix the problem? If so, you're probably initializing fliteController in -(void)viewDidLoad. What's the result of the two NSLog statements?
I've run into an issue with this little Objective-C project I'm doing and it's proving to be a bit of a roadblock. I'm playing around with Apple's NSSpeechRecognizer software on El Capitan, and I'm trying to get this guy running properly so that when the riddle I give it is posed to the user, the user can respond with a word to "do something cool". As it stands right now, the delegate method:
-(void) speechRecognizer:(NSSpeechRecognizer *)sender didRecognizeCommand:(NSString *)command { ... }`
is never even called, even though it appears the recognition icon is correctly detecting the answer to the riddle.
The problem is that your main function has a loop that is continually checking whether the speech has been recognizing. You are not giving NSSpeechRecognizer a chance to actually deliver any messages to you.
Your app needs to let the main "run loop" run, so it can deliver messages. Normally, in an OS X app, your main would just call NSApplicationMain, which does this for you.
Your code is effectively this:
#interface RecognizerDelegate : NSObject <NSSpeechRecognizerDelegate>
#property (nonatomic) NSSpeechRecognizer *recognizer;
#property (nonatomic) BOOL didRecognize;
#end
#implementation RecognizerDelegate
- (id)init
{
if ((self = [super init])) {
self.didRecognize = NO;
self.recognizer = [[NSSpeechRecognizer alloc] init];
self.recognizer.listensInForegroundOnly = NO;
self.recognizer.blocksOtherRecognizers = YES;
self.recognizer.delegate = self;
self.recognizer.commands = #[ #"hello" ];
[self.recognizer startListening];
}
return self;
}
- (void)speechRecognizer:(NSSpeechRecognizer *)sender didRecognizeCommand:(NSString *)command
{
self.didRecognize = YES;
}
#end
int main(int argc, const char * argv[])
{
#autoreleasepool
{
RecognizerDelegate *recognizerDelegate = [[RecognizerDelegate alloc] init];
while (recognizerDelegate.didRecognize == NO) {
// do nothing
}
NSLog(#"Recognized!");
}
return 0;
}
That while loop is doing nothing useful, just running your CPU in a loop and wasting time and energy. You are not letting any other code in NSSpeechSynthesizer, or any of the system frameworks like Foundation or AppKit, get the chance to do anything. So, nothing happens.
To fix this in the short term: you can let the main run loop run for a little while in each pass through the loop. This code would let the system run for a second, then would return to your code, so you could check again:
while (recognizerDelegate.didRecognize == NO) {
[[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:1.0]];
}
The longer-term fix would be to move your code out of main and to structure it like a real OS X app. Instead of using a loop to poll a condition like recognizerDelegate.didRecognize, you would just trigger the "next thing" directly from delegate methods like -speechRecognizer:didRecognizeCommand:, or you would use things like NSTimer to run code periodically.
For more details, see the Apple doc Cocoa Application Competencies for OS X, specifically the "Main Event Loop" section.
I had the same problem using NSSpeechRecognizer. The callback function:
func speechRecognizer(_ sender: NSSpeechRecognizer,
didRecognizeCommand command: String) {}
...was never called, even though everything appeared to be working.
There were three things I changed to get the code working.
1) I had to enable the entitlement in my "sandboxed" mode application to allow for microphone use.
... I also did these other two things, as well.
2) I added the "Privacy - Microphone Usage Description" in the info.pList, and set the string value to "I want to listen to you speak"
3) I added the "Privacy - Speech Recognition Usage Description" in the info.pList, and set the string value to "I want to write down what you say"
I am trying to intercept a keystroke, and substitute it with a different character. I have been able to intercept the key being pressed, as well as perform some extra operations. Now I need to hold the key being pressed if it matches one of the ones I am watching, and insert a different character. Here is the code I have right now:
#import "AppDelegate.h"
#import <AppKit/AppKit.h>
#import <CoreGraphics/CoreGraphics.h>
#include <ApplicationServices/ApplicationServices.h>
#interface AppDelegate ()
#property (weak) IBOutlet NSWindow *window;
#end
#implementation AppDelegate
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification {
//Keys that are being watched to be switched out
NSArray *keysToWatch = [[NSArray alloc] initWithObjects:#"c",#".", nil];
// register for keys throughout the device...
[NSEvent addGlobalMonitorForEventsMatchingMask:NSKeyDownMask
handler:^(NSEvent *event){
//Get characters
NSString *chars = [[event characters] lowercaseString];
//Get the actual character being pressed
unichar character = [chars characterAtIndex:0];
//Transform it to a string
NSString *aString = [NSString stringWithCharacters:&character length:1];
//If it is in the list, start looking if Keynote is active
if ([keysToWatch containsObject:[NSString stringWithString:aString]]) {
//DEBUG: Print a message
NSLog(#"Key being watched has been pressed");
//Get a list of all running apps
for (NSRunningApplication *currApp in [[NSWorkspace sharedWorkspace] runningApplications]) {
//Get current active app
if ([currApp isActive]) {
//Check if it is Keynote, if yes perform remap
if ([[currApp localizedName] isEqualToString:#"Keynote"]){
//DEBUG: Print a small message
NSLog(#"Current app is Keynote");
if (character=='.') {
NSLog(#"Pressed a dot");
//I want to post a different character here
PostKeyWithModifiers((CGKeyCode)11, FALSE);
}
else if ([aString isEqualToString:#"c"]) {
NSLog(#"Pressed c");
}
}
else if ([[currApp localizedName] isEqualToString:#"Microsoft PowerPoint"]){
}
}
}
}
}
];
}
- (void)applicationWillTerminate:(NSNotification *)aNotification {
// Insert code here to tear down your application
}
- (BOOL)acceptsFirstResponder {
return YES;
}
void PostKeyWithModifiers(CGKeyCode key, CGEventFlags modifiers)
{
CGEventSourceRef source = CGEventSourceCreate(kCGEventSourceStateCombinedSessionState);
CGEventRef keyDown = CGEventCreateKeyboardEvent(source, key, TRUE);
CGEventSetFlags(keyDown, modifiers);
CGEventRef keyUp = CGEventCreateKeyboardEvent(source, key, FALSE);
CGEventPost(kCGAnnotatedSessionEventTap, keyDown);
CGEventPost(kCGAnnotatedSessionEventTap, keyUp);
CFRelease(keyUp);
CFRelease(keyDown);
CFRelease(source);
}
#end
My problem is that I am not able to stop the original keystroke. Please keep in mind that I am completely new at Obj C, so let me know if there is anything that I can do better. Thanks!
From the docs for +[NSEvent addGlobalMonitorForEventsMatchingMask:handler:]:
Events are delivered asynchronously to your app and you can only
observe the event; you cannot modify or otherwise prevent the event
from being delivered to its original target application.
You would have to use a Quartz Event Tap for that.
So, after a LOT of digging around, I found a way to do this using Quartz Event Taps here. Thanks to #Ken Thomases for pointing me in the correct direction. I then combined my code with the one explained in this article and voilà, it works.
I am building a simple step sequencer in IRTcmix (version of RTcmix for the iphone/ipad), using UI Buttons to control which notes are on or off in the sequence.
I have been working with a set of RTcmix examples of other apps, and trying to piece my own together, but i'm unsure how to change values of a variable within the .sco (score file) with a toggle-style UIButton.
Here are some snippets from a working RTcmix App:
From the RTcmix manager .h:
#interface RTcmixManager : RTcmixPlayer {
RTcmixScore *polyScore;
}
#property (nonatomic, retain) RTcmixScore *polyScore;
- (void)noteOn:(int)note;
- (void)noteOff:(int)note;
#end
and the RTcmix manager .m
- (void)noteOn:(int)note {
[polyScore.mainScoreParameters replaceObjectAtIndex:0 withObject:[NSNumber numberWithInt:note]];
[polyScore.mainScoreParameters replaceObjectAtIndex:1 withObject:[NSNumber numberWithInt:1]];
[self parseScoreWithRTcmixScore:polyScore];
polyScore.setupIsActive = YES;
}
- (void)noteOff:(int)note {
[polyScore.mainScoreParameters replaceObjectAtIndex:0 withObject:[NSNumber numberWithInt:note]];
[polyScore.mainScoreParameters replaceObjectAtIndex:1 withObject:[NSNumber numberWithInt:0]];
[self parseScoreWithRTcmixScore:polyScore];
}
the viewcontroller .h:
#interface RTPolyphonyViewController : UIViewController {
RTcmixManager *rtcmixManager;
}
- (IBAction)keyDown:(UIButton *)sender;
- (IBAction)keyUp:(UIButton *)sender;
#end
viewcontroller .m:
- (IBAction)keyDown:(UIButton *)sender {
[rtcmixManager noteOn:sender.tag];
}
- (IBAction)keyUp:(UIButton *)sender {
[rtcmixManager noteOff:sender.tag];
}
and a snippet from the .sco file (the "%#" is the variables changed by user input):
NoteNumber = %#
NoteVelocity = %#
alreadySounding = note_exists(oscNotes[NoteNumber])
MAXMESSAGE(0, alreadySounding)
I am very new to objective C and xcode, I was wondering how to make a UI button toggle between two values in the score file (acting like a switch) for example, a button when toggled making the value of NoteNumber(in the score file) set to 1, while the value is 0 when toggled off. Because I am a beginner with Xcode(and objective C), I'm not sure about the proper syntax for making a button able to be toggled on and off, and i am also not sure how to connect said button with my .sco file, as much as I look at the sample code, I cannot seem to figure out how they all connect.
i would like to create a function called "playSound" with one (but later two parameters - one, which is the filename and one, which is the musictype (mp3,...))
but i cant get the paramter working in my function...please help me
here's my code
//
// MainView.m
//
// Created by Christopher Fuchs on 26.01.11.
// Copyright 2011 __MyCompanyName__. All rights reserved.
//
#import "MainView.h"
#import <AVFoundation/AVAudioPlayer.h>
#import <AVFoundation/AVFoundation.h>
#implementation MainView
void playSound(char filename){
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:#"Mein Alert" message:filename delegate:nil cancelButtonTitle:#"Abbrechen" otherButtonTitles:nil];
[alert show];
[alert release];
}
- (IBAction)PlayFatGuyWalking:(id)sender {
playSound(#"MusicFile");
}
#end
For now, i just added an alert instead of the playing sound framework
the paramater is called "filename" and should be called as message of the alert
thanks in advance
greez
- (void)playSound:(NSString *)filename {
// method body
}
// calling
[self playSound:#"MusicFile"];
Make this line:
void playSound(char filename)
into this:
void playSound(NSString *filename)
The # "foo" construction says "this is an NSString".
Also, if you want to keep it a C function, you might want to move it out of the #implementation block.
You will find that virtually everything asking for a string in Cocoa will want an NSString, and not a char *.
Oh yeah, and the (char filename) won't work in plain C either, that a single byte variable, not a pointer to a potential string.