I've created three sprites that works as walls in my scene. Then I have a sprite and a score. I want the sprite to set the score to 0 only when it touches the floor(one of the three sprites). So that's what I have for the contact.
- (void)didBeginContact:(SKPhysicsContact *)contact
{
SKPhysicsBody *firstBody, *secondBody;
if (contact.bodyA.categoryBitMask < contact.bodyB.categoryBitMask)
{
firstBody = contact.bodyA;
secondBody = contact.bodyB;
}
else
{
firstBody = contact.bodyB;
secondBody = contact.bodyA;
}
if ((firstBody.categoryBitMask & shipCategory) != 0 &&
(secondBody.categoryBitMask & obstacleCategory) != 0)
{
score = 0;
myLabel.text = [NSString stringWithFormat:#"%i", score];
}
}
Here are the categoryBitMask
static const uint32_t shipCategory = 0x1 << 1;
static const uint32_t obstacleCategory = 0x1 << 1;
static const uint32_t wallCategory = 0x1 << 1;
Theese are the codes for the sprite, the floor and the walls
-(SKSpriteNode *)floorNode
{
floorNode = [SKSpriteNode spriteNodeWithImageNamed:#"rectangle.png"];
floorNode.position = CGPointMake(160,100);
floorNode.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:floorNode.size];
floorNode.physicsBody.categoryBitMask = obstacleCategory;
floorNode.physicsBody.contactTestBitMask = shipCategory;
fireNode.physicsBody.usesPreciseCollisionDetection = YES;
fireNode.physicsBody.collisionBitMask = 2;
floorNode.physicsBody.dynamic = NO;
floorNode.zPosition = 1.0;
return floorNode;
}
-(SKSpriteNode *)walldxNode
{
walldxNode = [SKSpriteNode spriteNodeWithImageNamed:#"wall.png"];
walldxNode.position = CGPointMake(30, 568);
walldxNode.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:walldxNode.size];
walldxNode.physicsBody.categoryBitMask = wallCategory;
walldxNode.physicsBody.dynamic = NO;
return walldxNode;
}
-(SKSpriteNode *)wallsxNode
{
wallsxNode = [SKSpriteNode spriteNodeWithImageNamed:#"wall.png"];
wallsxNode.position = CGPointMake(290, 568);
wallsxNode.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:wallsxNode.size];
wallsxNode.physicsBody.categoryBitMask = wallCategory;
wallsxNode.physicsBody.dynamic = NO;
return wallsxNode;
}
-(SKSpriteNode *)fireButtonNode
{
fireNode = [SKSpriteNode spriteNodeWithImageNamed:#"Spaceship.png"];
fireNode.position = CGPointMake(160,450);
fireNode.xScale = 0.32;
fireNode.yScale = 0.32;
fireNode.physicsBody = [SKPhysicsBody bodyWithCircleOfRadius: fireNode.size.height/2];
fireNode.physicsBody.categoryBitMask = shipCategory;
fireNode.physicsBody.dynamic = YES;
fireNode.physicsBody.contactTestBitMask = obstacleCategory;
fireNode.physicsBody.collisionBitMask = 2;
fireNode.physicsBody.usesPreciseCollisionDetection = YES;
fireNode.name = #"fireButtonNode";//how the node is identified later
fireNode.zPosition = 2.0;
return fireNode;
}
The problem is that the sprite sets the score to 0 also when collides with the others two walls, which have differents categoryBitMask. I don't know what to do.
Your category bitmasks all have the same values. Make them differ like this:
static const uint32_t shipCategory = 0x1 << 1; // this equals 2
static const uint32_t obstacleCategory = 0x1 << 2; // this equals 4
static const uint32_t wallCategory = 0x1 << 3; // this equals 8
Batalia is correct, but these days you should probably use an enum rather than some crusty old static const. I did it in some code like this and then filed a bug against the sample code for not using Apple's own current best practices:
// These constans are used to define the physics interactions between physics bodies in the scene.
typedef NS_OPTIONS(NSUInteger, RockBusterCollionsMask) {
RBCmissileCategory = 1 << 0,
RBCasteroidCategory = 1 << 1,
RBCshipCategory = 1 << 2
};
Related
CAEmitterCell contents picCAEmitterLayer emits multiple CAEmitterCells with specified trajectories. When the trajectory CAEmitterCells fly to the end of time, it will become a particle graph displayed by CAEmitterCells, and then the particle graph will be scattered 360 °. However, only one trajectory particle can be animated completely, but more than two particles will appear. The trajectory particle graph will turn white
Adding a boomEmitterCell will cause the contents of CAEmitterCell in getDogLeftDownEmitterWithImageName and getRedLeftEmitterWithImageName to not display the picture and white blocks
CAEmitterLayer *emitterLayer = [[CAEmitterLayer alloc] init];
emitterLayer.emitterPosition = CGPointMake(self.view.layer.bounds.size.width -100, self.view.layer.bounds.size.height - 100);
emitterLayer.emitterSize = CGSizeMake(50, 100.f);
emitterLayer.emitterShape = kCAEmitterLayerLine;
emitterLayer.emitterMode = kCAEmitterLayerOutline;
emitterLayer.renderMode = kCAEmitterLayerOldestLast;
CAEmitterCell *dogleftEmitterCell = [self getRedLeftEmitterWithImageName:[imageArray objectAtIndex:0]];
CAEmitterCell *redLeftEmitterCell = [self getRedLeftEmitterWithImageName:[imageArray objectAtIndex:0]];
emitterLayer.emitterCells = #[dogleftEmitterCell,redLeftEmitterCell];
[self.view.layer addSublayer:emitterLayer];
//狗头左下
- (CAEmitterCell *)getDogLeftDownEmitterWithImageName:(NSString *)imageName {
CAEmitterCell *emitterCell = [[CAEmitterCell alloc] init];
emitterCell.name = #"左下狗头";
emitterCell.contents = (__bridge id _Nullable)[UIImage imageNamed:#"emoji_6"].CGImage;
//产生频率
emitterCell.birthRate = 1;
//存活时长
emitterCell.lifetime = 0.6;
//速度
emitterCell.velocity = 100;
emitterCell.xAcceleration = -1000.f; // 模拟重力影响
emitterCell.scale = 0.2;
emitterCell.scaleSpeed = 0.25;
emitterCell.emissionLongitude = M_PI_2;
// emitterCell.emissionRange = M_PI_4;
emitterCell.emitterCells = #[[self boomEmitterCell]];
return emitterCell;
}
//红包左上
- (CAEmitterCell *)getRedLeftEmitterWithImageName:(NSString *)imageName {
CAEmitterCell *emitterCell = [[CAEmitterCell alloc] init];
emitterCell.name = #"红包";
emitterCell.contents = (__bridge id _Nullable)[UIImage imageNamed:#"emoji_7"].CGImage;
//产生频率
emitterCell.birthRate = 10;
//存活时长
emitterCell.lifetime = 0.6;
// emitterCell.beginTime = self.beginTime;
//速度
emitterCell.velocity = 100;
emitterCell.yAcceleration = -1000.f; // 模拟重力影响
emitterCell.scale = 0.2;
// emitterCell.scaleRange = 0.06;
emitterCell.scaleSpeed = 0.25;
emitterCell.emissionLongitude = M_PI;
// CAEmitterCell *emitterCell = [self boomEmitterCell];
emitterCell.emitterCells = #[[self boomEmitterCell]];
return emitterCell;
}
- (CAEmitterCell *)boomEmitterCell {
// 爆炸
CAEmitterCell * explodeCell = [CAEmitterCell emitterCell];
explodeCell.name = #"explodeCell";
explodeCell.birthRate = 2.f;
explodeCell.lifetime = 0.6f;
// explodeCell.velocity = 0.f;
// explodeCell.scale = 1.0;
// explodeCell.redSpeed = -1.5; //爆炸的时候变化颜色
// explodeCell.blueRange = 1.5; //爆炸的时候变化颜色
// explodeCell.greenRange = 1.f; //爆炸的时候变化颜色
// explodeCell.birthRate = 1.0;
// explodeCell.velocity = 0;
// explodeCell.scale = 2.5;
// explodeCell.redSpeed =-1.5;
// explodeCell.blueSpeed =+1.5;
// explodeCell.greenSpeed =+1.0;
// explodeCell.lifetime = 0.35;
explodeCell.contents = (__bridge id _Nullable)[[UIImage imageNamed:#"allStart"] CGImage];
// 火花
// CAEmitterCell * sparkCell = [CAEmitterCell emitterCell];
// sparkCell.name = #"sparkCell";
//
// sparkCell.birthRate = 3.f;
// sparkCell.lifetime = 3.f;
// sparkCell.velocity = 125.f;
//// sparkCell.yAcceleration = 75.f; // 模拟重力影响
// sparkCell.emissionRange = M_PI * 2; // 360度
//
// sparkCell.scale = 1.2f;
// sparkCell.contents = (id)[[UIImage imageNamed:#"star_white_stroke"] CGImage];
// sparkCell.redSpeed = 0.4;
// sparkCell.greenSpeed = -0.1;
// sparkCell.blueSpeed = -0.1;
// sparkCell.alphaSpeed = -0.25;
// explodeCell.emitterCells = #[sparkCell];
return explodeCell;
}
I've found code for making a rope in my Sprite Kit game. I'll post it below. Problem is, the method header is very long & has a syntax that I'm unable to call upon in my initWithSize method. I tried writing [self addRopeJointItems]; but to no avail. Yes. I am new to objective-c. Hope someone can help out.
Code:
+(void)addRopeJointItems:(CGPoint)leftStartPosition countJointElements:(int)countJointElements game:(SKScene*)game
{
int itemJointWidth = 25;
//Left Physics Anchor
SKSpriteNode * leftAnchor = [SKSpriteNode spriteNodeWithColor:[SKColor clearColor] size:CGSizeMake(1, 1)];
leftAnchor.position = CGPointMake(leftStartPosition.x, leftStartPosition.y);
//leftAnchor.size = CGSizeMake(1, 1);
leftAnchor.zPosition = 2;
leftAnchor.physicsBody = [SKPhysicsBody
bodyWithRectangleOfSize:
leftAnchor.size];
leftAnchor.physicsBody.affectedByGravity = false;
leftAnchor.physicsBody.mass = 99999999999;
[game addChild:leftAnchor];
//add RopeElements
for (int i=0; i<countJointElements; i++)
{
SKSpriteNode * item = [SKSpriteNode spriteNodeWithImageNamed:#"rope_ring.png"];
item.name = [NSString stringWithFormat:#"ropeitem_%d", i];
item.position = CGPointMake(leftStartPosition.x + (i*itemJointWidth) + itemJointWidth/2, leftStartPosition.y+60);
item.size = CGSizeMake(itemJointWidth + 5, 5);
item.zPosition = 2;
item.physicsBody = [SKPhysicsBody
bodyWithRectangleOfSize:
item.size];
item.physicsBody.categoryBitMask = kNilOptions;
[game addChild:item];
//Add Joint to the element before
SKPhysicsBody* bodyA;
if (i == 0)
{
bodyA = leftAnchor.physicsBody;
}
else
{
bodyA = [game childNodeWithName:[NSString stringWithFormat:#"ropeitem_%d", i-1]].physicsBody;
}
SKPhysicsJointPin* joint = [SKPhysicsJointPin jointWithBodyA:bodyA bodyB:item.physicsBody anchor:CGPointMake((item.position.x - item.size.width/2) + 5, item.position.y)];
[game.physicsWorld addJoint:joint];
}
//Right Physics Anchor
SKSpriteNode * rightAnchor = [SKSpriteNode spriteNodeWithColor:[SKColor clearColor] size:CGSizeMake(1, 1)];
rightAnchor.position = CGPointMake((leftStartPosition.x + (countJointElements*itemJointWidth)),
leftStartPosition.y+60);
//rightAnchor.size = CGSizeMake(1, 1);
rightAnchor.zPosition = 2;
rightAnchor.physicsBody = [SKPhysicsBody
bodyWithRectangleOfSize:
rightAnchor.size];
rightAnchor.physicsBody.affectedByGravity = false;
rightAnchor.physicsBody.mass = 99999999999;
[game addChild:rightAnchor];
//Add the Last Joint
SKPhysicsJointPin* jointLast = [SKPhysicsJointPin jointWithBodyA:[game childNodeWithName:[NSString stringWithFormat:#"ropeitem_%d", countJointElements - 1]].physicsBody
bodyB:rightAnchor.physicsBody
anchor:rightAnchor.position];
[game.physicsWorld addJoint:jointLast];
}
You can do two things here:-
1) Either change your method to instance method with prefix (-). If you want call as instance method with self
-(void)addRopeJointItems:(CGPoint)leftStartPosition countJointElements:(int)countJointElements game:(SKScene*)game
2)Or follow #Andrey Chernukha comments as
[ClassName addRopeJointItems:leftStartPosition countJointElements:elements game:game];
sorry for re-opening this title but none of the titles about this question helped me that much. In my game you are a space ship and you trying to escape from the incoming asteroids. When your ship touches an asteroid, you explode.
My codes are like these;
static const uint32_t asteroidCategory = 1;
static const uint32_t playerCategory = 2;
static const uint32_t laserCategory = 3;
player.physicsBody.categoryBitMask = playerCategory;
player.physicsBody.contactTestBitMask = asteroidCategory;
player.physicsBody.collisionBitMask = 0;
asteroid.physicsBody.categoryBitMask=asteroidCategory;
asteroid.physicsBody.contactTestBitMask=playerCategory;
asteroid.physicsBody.collisionBitMask=0;
But I don't know what should I do at 'didBeginContact' method.
please help me, thank you...
Try starting with
-(void)didBeginContact:(SKPhysicsContact *)contact
{
SKPhysicsBody *firstBody, *secondBody;
if (contact.bodyA.categoryBitMask < contact.bodyB.categoryBitMask)
{
firstBody = contact.bodyA;
secondBody = contact.bodyB;
}
else
{
firstBody = contact.bodyB;
secondBody = contact.bodyA;
}
if ((firstBody.categoryBitMask & playerCategory) != 0 &&
(secondBody.categoryBitMask & astroidCategory) != 0)
{
[self player:(SKSpriteNode *) firstBody.node didCollideWithAstroid:(SKSpriteNode *) secondBody.node];
}
You also need to make a method for 'didCollideWithAstroid'
I'm creating a old school music emulator for the old GWBasic PLAY command. To that end I have a tone generator and a music player. Between each of the notes played I'm getting a chirp sound that mucking things up. Below are both of my classes:
ToneGen.h
#import <Foundation/Foundation.h>
#interface ToneGen : NSObject
#property (nonatomic) id delegate;
#property (nonatomic) double frequency;
#property (nonatomic) double sampleRate;
#property (nonatomic) double theta;
- (void)play:(float)ms;
- (void)play;
- (void)stop;
#end
ToneGen.m
#import <AudioUnit/AudioUnit.h>
#import "ToneGen.h"
OSStatus RenderTone(
void *inRefCon,
AudioUnitRenderActionFlags *ioActionFlags,
const AudioTimeStamp *inTimeStamp,
UInt32 inBusNumber,
UInt32 inNumberFrames,
AudioBufferList *ioData);
void ToneInterruptionListener(void *inClientData, UInt32 inInterruptionState);
#interface ToneGen()
#property (nonatomic) AudioComponentInstance toneUnit;
#property (nonatomic) NSTimer *timer;
- (void)createToneUnit;
#end
#implementation ToneGen
#synthesize toneUnit = _toneUnit;
#synthesize timer = _timer;
#synthesize delegate = _delegate;
#synthesize frequency = _frequency;
#synthesize sampleRate = _sampleRate;
#synthesize theta = _theta;
- (id) init
{
self = [super init];
if (self)
{
self.sampleRate = 44100;
self.frequency = 1440.0f;
return self;
}
return nil;
}
- (void)play:(float)ms
{
[self play];
self.timer = [NSTimer scheduledTimerWithTimeInterval:(ms / 100)
target:self
selector:#selector(stop)
userInfo:nil
repeats:NO];
[[NSRunLoop mainRunLoop] addTimer:self.timer forMode:NSRunLoopCommonModes];
}
- (void)play
{
if (!self.toneUnit)
{
[self createToneUnit];
// Stop changing parameters on the unit
OSErr err = AudioUnitInitialize(self.toneUnit);
if (err)
DLog(#"Error initializing unit");
// Start playback
err = AudioOutputUnitStart(self.toneUnit);
if (err)
DLog(#"Error starting unit");
}
}
- (void)stop
{
[self.timer invalidate];
self.timer = nil;
if (self.toneUnit)
{
AudioOutputUnitStop(self.toneUnit);
AudioUnitUninitialize(self.toneUnit);
AudioComponentInstanceDispose(self.toneUnit);
self.toneUnit = nil;
}
if(self.delegate && [self.delegate respondsToSelector:#selector(toneStop)]) {
[self.delegate performSelector:#selector(toneStop)];
}
}
- (void)createToneUnit
{
AudioComponentDescription defaultOutputDescription;
defaultOutputDescription.componentType = kAudioUnitType_Output;
defaultOutputDescription.componentSubType = kAudioUnitSubType_DefaultOutput;
defaultOutputDescription.componentManufacturer = kAudioUnitManufacturer_Apple;
defaultOutputDescription.componentFlags = 0;
defaultOutputDescription.componentFlagsMask = 0;
// Get the default playback output unit
AudioComponent defaultOutput = AudioComponentFindNext(NULL, &defaultOutputDescription);
if (!defaultOutput)
DLog(#"Can't find default output");
// Create a new unit based on this that we'll use for output
OSErr err = AudioComponentInstanceNew(defaultOutput, &_toneUnit);
if (err)
DLog(#"Error creating unit");
// Set our tone rendering function on the unit
AURenderCallbackStruct input;
input.inputProc = RenderTone;
input.inputProcRefCon = (__bridge void*)self;
err = AudioUnitSetProperty(self.toneUnit,
kAudioUnitProperty_SetRenderCallback,
kAudioUnitScope_Input,
0,
&input,
sizeof(input));
if (err)
DLog(#"Error setting callback");
// Set the format to 32 bit, single channel, floating point, linear PCM
const int four_bytes_per_float = 4;
const int eight_bits_per_byte = 8;
AudioStreamBasicDescription streamFormat;
streamFormat.mSampleRate = self.sampleRate;
streamFormat.mFormatID = kAudioFormatLinearPCM;
streamFormat.mFormatFlags =
kAudioFormatFlagsNativeFloatPacked | kAudioFormatFlagIsNonInterleaved;
streamFormat.mBytesPerPacket = four_bytes_per_float;
streamFormat.mFramesPerPacket = 1;
streamFormat.mBytesPerFrame = four_bytes_per_float;
streamFormat.mChannelsPerFrame = 1;
streamFormat.mBitsPerChannel = four_bytes_per_float * eight_bits_per_byte;
err = AudioUnitSetProperty (self.toneUnit,
kAudioUnitProperty_StreamFormat,
kAudioUnitScope_Input,
0,
&streamFormat,
sizeof(AudioStreamBasicDescription));
if (err)
DLog(#"Error setting stream format");
}
#end
OSStatus RenderTone(
void *inRefCon,
AudioUnitRenderActionFlags *ioActionFlags,
const AudioTimeStamp *inTimeStamp,
UInt32 inBusNumber,
UInt32 inNumberFrames,
AudioBufferList *ioData)
{
// Fixed amplitude is good enough for our purposes
const double amplitude = 0.25;
// Get the tone parameters out of the view controller
ToneGen *toneGen = (__bridge ToneGen *)inRefCon;
double theta = toneGen.theta;
double theta_increment = 2.0 * M_PI * toneGen.frequency / toneGen.sampleRate;
// This is a mono tone generator so we only need the first buffer
const int channel = 0;
Float32 *buffer = (Float32 *)ioData->mBuffers[channel].mData;
// Generate the samples
for (UInt32 frame = 0; frame < inNumberFrames; frame++)
{
buffer[frame] = sin(theta) * amplitude;
theta += theta_increment;
if (theta > 2.0 * M_PI)
{
theta -= 2.0 * M_PI;
}
}
// Store the theta back in the view controller
toneGen.theta = theta;
return noErr;
}
void ToneInterruptionListener(void *inClientData, UInt32 inInterruptionState)
{
ToneGen *toneGen = (__bridge ToneGen *)inClientData;
[toneGen stop];
}
Music.h
#import <Foundation/Foundation.h>
#interface Music : NSObject
- (void) play:(NSString *)music;
- (void) stop;
#end
Music.m
#import "Music.h"
#import "ToneGen.h"
#interface Music()
#property (nonatomic, readonly) ToneGen *toneGen;
#property (nonatomic, assign) int octive;
#property (nonatomic, assign) int tempo;
#property (nonatomic, assign) int length;
#property (nonatomic, strong) NSData *music;
#property (nonatomic, assign) int dataPos;
#property (nonatomic, assign) BOOL isPlaying;
- (void)playNote;
#end
#implementation Music
#synthesize toneGen = _toneGen;
- (ToneGen*)toneGen
{
if (_toneGen == nil)
{
_toneGen = [[ToneGen alloc] init];
_toneGen.delegate = self;
}
return _toneGen;
}
#synthesize octive = _octive;
- (void)setOctive:(int)octive
{
// Sinity Check
if (octive < 0)
octive = 0;
if (octive > 6)
octive = 6;
_octive = octive;
}
#synthesize tempo = _tempo;
- (void)setTempo:(int)tempo
{
// Sinity Check
if (tempo < 30)
tempo = 30;
if (tempo > 255)
tempo = 255;
_tempo = tempo;
}
#synthesize length = _length;
- (void)setLength:(int)length
{
// Sinity Check
if (length < 1)
length = 1;
if (length > 64)
length = 64;
_length = length;
}
#synthesize music = _music;
#synthesize dataPos = _dataPos;
#synthesize isPlaying = _isPlaying;
- (id)init
{
self = [super init];
if (self)
{
self.octive = 4;
self.tempo = 120;
self.length = 1;
return self;
}
return nil;
}
- (void) play:(NSString *)music
{
DLog(#"%#", music);
self.music = [[music stringByReplacingOccurrencesOfString:#"+" withString:#"#"]
dataUsingEncoding: NSASCIIStringEncoding];
self.dataPos = 0;
self.isPlaying = YES;
[self playNote];
}
- (void)stop
{
self.isPlaying = NO;
}
- (void)playNote
{
if (!self.isPlaying)
return;
if (self.dataPos > self.music.length || self.music.length == 0) {
self.isPlaying = NO;
return;
}
unsigned char *data = (unsigned char*)[self.music bytes];
unsigned int code = (unsigned int)data[self.dataPos];
self.dataPos++;
switch (code) {
case 65: // A
case 66: // B
case 67: // C
case 68: // D
case 69: // E
case 70: // F
case 71: // G
{
// Peak at the next char to look for sharp or flat
bool sharp = NO;
bool flat = NO;
if (self.dataPos < self.music.length) {
unsigned int peak = (unsigned int)data[self.dataPos];
if (peak == 35) // #
{
self.dataPos++;
sharp = YES;
}
else if (peak == 45) // -
{
self.dataPos++;
flat = YES;
}
}
// Peak ahead for a length changes
bool look = YES;
int count = 0;
int newLength = 0;
while (self.dataPos < self.music.length && look) {
unsigned int peak = (unsigned int)data[self.dataPos];
if (peak >= 48 && peak <= 57)
{
peak -= 48;
int n = (count * 10);
if (n == 0) { n = 1; }
newLength += peak * n;
self.dataPos++;
} else {
look = NO;
}
}
// Pick the note length
int length = self.length;
if (newLength != 0)
{
DLog(#"InlineLength: %d", newLength);
length = newLength;
}
// Create the note string
NSString *note = [NSString stringWithFormat:#"%c", code];
if (sharp)
note = [note stringByAppendingFormat:#"#"];
else if (flat)
note = [note stringByAppendingFormat:#"-"];
// Set the tone generator freq
[self setFreq:[self getNoteNumber:note]];
// Play the note
[self.toneGen play:(self.tempo / length)];
}
break;
case 76: // L (length)
{
bool look = YES;
int newLength = 0;
while (self.dataPos < self.music.length && look) {
unsigned int peak = (unsigned int)data[self.dataPos];
if (peak >= 48 && peak <= 57)
{
peak -= 48;
newLength = newLength * 10 + peak;
self.dataPos++;
} else {
look = NO;
}
}
self.length = newLength;
DLog(#"Length: %d", self.length);
[self playNote];
}
break;
case 79: // O (octive)
{
bool look = YES;
int newOctive = 0;
while (self.dataPos < self.music.length && look) {
unsigned int peak = (unsigned int)data[self.dataPos];
if (peak >= 48 && peak <= 57)
{
peak -= 48;
newOctive = newOctive * 10 + peak;
self.dataPos++;
} else {
look = NO;
}
}
self.octive = newOctive;
DLog(#"Octive: %d", self.self.octive);
[self playNote];
}
break;
case 84: // T (tempo)
{
bool look = YES;
int newTempo = 0;
while (self.dataPos < self.music.length && look) {
unsigned int peak = (unsigned int)data[self.dataPos];
if (peak >= 48 && peak <= 57)
{
peak -= 48;
newTempo = newTempo * 10 + peak;
self.dataPos++;
} else {
look = NO;
}
}
self.tempo = newTempo;
DLog(#"Tempo: %d", self.self.tempo);
[self playNote];
}
break;
default:
[self playNote];
break;
}
}
- (int)getNoteNumber:(NSString*)note
{
note = [note uppercaseString];
DLog(#"%#", note);
if ([note isEqualToString:#"A"])
return 0;
else if ([note isEqualToString:#"A#"] || [note isEqualToString:#"B-"])
return 1;
else if ([note isEqualToString:#"B"] || [note isEqualToString:#"C-"])
return 2;
else if ([note isEqualToString:#"C"] || [note isEqualToString:#"B#"])
return 3;
else if ([note isEqualToString:#"C#"] || [note isEqualToString:#"D-"])
return 4;
else if ([note isEqualToString:#"D"])
return 5;
else if ([note isEqualToString:#"D#"] || [note isEqualToString:#"E-"])
return 6;
else if ([note isEqualToString:#"E"] || [note isEqualToString:#"F-"])
return 7;
else if ([note isEqualToString:#"F"] || [note isEqualToString:#"E#"])
return 8;
else if ([note isEqualToString:#"F#"] || [note isEqualToString:#"G-"])
return 9;
else if ([note isEqualToString:#"G"])
return 10;
else if ([note isEqualToString:#"G#"])
return 11;
}
- (void)setFreq:(int)note
{
float a = powf(2, self.octive);
float b = powf(1.059463, note);
float freq = roundf((275.0 * a * b) / 10);
self.toneGen.frequency = freq;
}
- (void)toneStop
{
[self playNote];
}
#end
To play little tune create a Music object and play...
[self.music play:#"T180 DF#A L2 A L4 O4 AA P4 F#F# P4 O3 D DF#A L2 A L4 O4 AA P4 GG P4 O3 C#C#EB L2 B L4 O4 BB P4 GG P4 O3 C#C#EB L2 B L4 O4 BB P4 F+F+ P4 O3 DDF#A L2 O4 D L4 O5 DD P4O4 AA P4 O3 DDF#A L2 O4 D L4 O5 DD P4O4 BB P4 EEG L8 B P8 ML B1 L4 MN G#A ML L3 O5 F#1L4 MN D O4 F# ML L2 F# MN L4 E ML L2 B MN L4 AD P8 D8 D4"];
Any idea on how to remove the chirp between notes?
I think that the bit where you stop audio output between notes is the culprit:
if (self.toneUnit)
{
AudioOutputUnitStop(self.toneUnit);
AudioUnitUninitialize(self.toneUnit);
AudioComponentInstanceDispose(self.toneUnit);
self.toneUnit = nil;
}
Just leave the tone unit active and you'll have less chirping. You'll need some other way to generate silence, probably by having RenderTone continue to run but generate amplitude zero.
I was able to eliminate the slight chirps that remained by having it, on a frequency change, fade the amplitude down to nothing, update the frequenmcy, and fade back in again. This is of course what the old PC speaker couldn't do (except for a few people who rapidly switched it on again), but with a very rapid fade you can probably get the old-school effect without the chirps.
Here's my fading RenderTone function (currently using evil global variables):
double currentFrequency=0;
double currentSampleRate=0;
double currentAmplitude=0;
OSStatus RenderTone(
void *inRefCon,
AudioUnitRenderActionFlags *ioActionFlags,
const AudioTimeStamp *inTimeStamp,
UInt32 inBusNumber,
UInt32 inNumberFrames,
AudioBufferList *ioData)
{
// Fixed amplitude is good enough for our purposes
const double amplitude = 0.5;
// Get the tone parameters out of the view controller
ToneGen *toneGen = (__bridge ToneGen *)inRefCon;
double theta = toneGen.theta;
BOOL fadingOut = NO;
if ((currentFrequency != toneGen.frequency) || (currentSampleRate != toneGen.sampleRate))
{
if (currentAmplitude > DBL_EPSILON)
{
fadingOut = YES;
}
else
{
currentFrequency = toneGen.frequency;
currentSampleRate = toneGen.sampleRate;
}
}
double theta_increment = 2.0 * M_PI * currentFrequency /currentSampleRate;
// This is a mono tone generator so we only need the first buffer
const int channel = 0;
Float32 *buffer = (Float32 *)ioData->mBuffers[channel].mData;
// Generate the samples
for (UInt32 frame = 0; frame < inNumberFrames; frame++)
{
buffer[frame] = sin(theta) * currentAmplitude;
//NSLog(#"amplitude = %f", currentAmplitude);
theta += theta_increment;
if (theta > 2.0 * M_PI)
{
theta -= 2.0 * M_PI;
}
if (fadingOut)
{
if (currentAmplitude > 0)
{
currentAmplitude -= 0.001;
if (currentAmplitude < 0)
currentAmplitude = 0;
}
}
else
{
if (currentAmplitude < amplitude)
{
currentAmplitude += 0.001;
if (currentAmplitude > amplitude)
currentAmplitude = amplitude;
}
}
}
// Store the theta back in the view controller
toneGen.theta = theta;
return noErr;
}
That little chirp is generally an artifact of mathematics. The ear essentially analyzes input signals in the frequency domain. A steady sine wave at frequency 220 Hz, for example, will sound like an A. However, when your sine wave is *un*steady, there are other frequencies that show up due to the boundary. In particular, you get a bit of a pop due to the very high frequency component of starting or stopping a sound abruptly.
The way I solved this in my synthesizer (in Javascript, not Obj-C, but the concept here is the same) is to fade the sound in over 300 samples or so on note on, and fade the sound out over 300 samples or so on note off. There's no way to truly eliminate boundary effects other than not having a boundary at all, but even a small and imperceptible amount of fade will render the boundary effect imperceptible as well.
I have a class that will let me play a tone using audio units, what i would like to be able to do is have the class play morse code style when i send the class a phrase or letter.
How would i go about this? I'm hoping someone can point me in the right direction. i have included the tone generator .h and .m files below
//
// Singer.h
// musiculesdev
//
// Created by Dylan on 2/20/09.
// Copyright 2009 __MyCompanyName__. All rights reserved.
//
#import <Foundation/Foundation.h>
#import <AudioUnit/AudioUnit.h>
#interface Singer : NSObject {
AudioComponentInstance audioUnit;
}
-(void)initAudio; // put this in init?
-(void)start;
-(void)stop;
-(IBAction)turnOnSound:(id)sender;
#end
//
// Singer.m
// musiculesdev
//
// Created by Dylan on 2/20/09.
// Copyright 2009 __MyCompanyName__. All rights reserved.
//
#import <AudioUnit/AudioUnit.h>
#import <math.h>
#import "Singer.h"
#define kOutputBus 0
#define kSampleRate 44100
//44100.0f
#define kWaveform (M_PI * 2.0f / kSampleRate)
#implementation Singer
OSStatus playbackCallback(void *inRefCon,
AudioUnitRenderActionFlags *ioActionFlags,
const AudioTimeStamp *inTimeStamp,
UInt32 inBusNumber,
UInt32 inNumberFrames,
AudioBufferList *ioData) {
//Singer *me = (Singer *)inRefCon;
static int phase = 0;
for(UInt32 i = 0; i < ioData->mNumberBuffers; i++) {
int samples = ioData->mBuffers[i].mDataByteSize / sizeof(SInt16);
SInt16 values[samples];
float waves;
for(int j = 0; j < samples; j++) {
waves = 0;
waves += sin(kWaveform * 261.63f * phase);
waves += sin(kWaveform * 120.0f * phase);
waves += sin(kWaveform * 1760.3f * phase);
waves += sin(kWaveform * 880.0f * phase);
waves *= 32500 / 4; // <--------- make sure to divide by how many waves you're stacking
values[j] = (SInt16)waves;
values[j] += values[j]<<16;
phase++;
}
memcpy(ioData->mBuffers[i].mData, values, samples * sizeof(SInt16));
}
return noErr;
}
-(IBAction)turnOnSound:(id)sender {
Singer *singer = [[Singer alloc] init];
[singer start];
}
-(id)init {
NSLog(#"In the singer init!!");
if(self = [super init]) {
[self initAudio];
}
return self;
}
-(void)initAudio {
OSStatus status;
AudioComponentDescription desc;
desc.componentType = kAudioUnitType_Output;
desc.componentSubType = kAudioUnitSubType_RemoteIO;
desc.componentFlags = 0;
desc.componentFlagsMask = 0;
desc.componentManufacturer = kAudioUnitManufacturer_Apple;
AudioComponent outputComponent = AudioComponentFindNext(NULL, &desc);
status = AudioComponentInstanceNew(outputComponent, &audioUnit);
UInt32 flag = 1;
status = AudioUnitSetProperty(audioUnit, kAudioOutputUnitProperty_EnableIO, kAudioUnitScope_Output, kOutputBus, &flag, sizeof(flag));
AudioStreamBasicDescription audioFormat;
audioFormat.mSampleRate = kSampleRate;
audioFormat.mFormatID = kAudioFormatLinearPCM;
audioFormat.mFormatFlags = kAudioFormatFlagIsSignedInteger | kAudioFormatFlagIsPacked;
audioFormat.mFramesPerPacket = 1;
audioFormat.mChannelsPerFrame = 1;
audioFormat.mBitsPerChannel = 16;
audioFormat.mBytesPerPacket = 2;
audioFormat.mBytesPerFrame = 2;
status = AudioUnitSetProperty(audioUnit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Input, kOutputBus, &audioFormat, sizeof(audioFormat));
AURenderCallbackStruct callbackStruct;
callbackStruct.inputProc = playbackCallback;
callbackStruct.inputProcRefCon = self;
status = AudioUnitSetProperty(audioUnit, kAudioUnitProperty_SetRenderCallback, kAudioUnitScope_Global, kOutputBus, &callbackStruct, sizeof(callbackStruct));
status = AudioUnitInitialize(audioUnit);
}
-(void)start {
OSStatus status;
status = AudioOutputUnitStart(audioUnit);
}
-(void)stop {
OSStatus status;
status = AudioOutputUnitStop(audioUnit);
}
-(void)dealloc {
AudioUnitUninitialize(audioUnit);
[super dealloc];
}
#end
You need to be able to generate tones of a specific duration, separated by silences of a specific duration. So long as you have these two building blocks you can send morse code:
dot = 1 unit
dash = 3 units
space between dots/dashes within a letter = 1 unit
space between letters = 3 units
space between words = 5 units
The length of unit determines the overall speed of the morse code. Start with e.g. 50 ms.
The tone should just be a pure sine wave at an appropriate frequency, e.g. 400 Hz. The silence can just be an alternate buffer containing all zeroes. That way you can "play" both the tone and the silence using the same API, without worrying about timing/synchronisation, etc.