source: orbit/iOS/Orbit/Orbit/AudioGenerator.m @ 9b2949b

ServoTab_Interfacepyramid
Last change on this file since 9b2949b was 9b2949b, checked in by Steve Castellotti <sc@…>, 7 years ago

iOS:

  • pre-signal hacking
  • Property mode set to 100644
File size: 12.8 KB
Line 
1//
2//  AudioGenerator.m
3//  orbit
4//
5//  Created by Jonathon Horsman on 19/07/2013.
6//  Copyright (c) 2013 Puzzlebox Productions, LLC. All rights reserved.
7//
8
9#import "AudioGenerator.h"
10
11#define sampleRate 44100
12
13/**
14 * Half periods in the audio code, in seconds.
15 */
16#define longHIGH 0.000829649
17#define longLOW 0.000797027
18#define shortHIGH 0.000412649
19#define shortLOW 0.000378351
20
21@implementation AudioGenerator {
22    float sampleTime;
23        AudioComponentInstance toneUnit;
24   
25    int longHighLength, shortHighLength, longZeroLength, longLowLength, mediumLowLength, shortLowLength, waveBitLength;
26    float *waveLongHigh, *waveShortHigh, *waveLongZero, *waveLongLow, *waveMediumLow, *waveShortLow, *waveBit;
27   
28    int MAX_BUFFER_SIZE;
29}
30
31@synthesize yaw, pitch, throttle;
32
33- (id) init
34{
35    self = [super init];
36    if (self) {
37        sampleTime = 1 / (float)sampleRate;
38       
39        [self prepareStaticArrays];
40    }
41    return self;
42}
43
44- (void) prepareStaticArrays
45{
46   
47    waveLongHigh = [self generateHalfSine:true halfPeriod:longHIGH];
48    longHighLength = [self arraySizeWithHalfPeriod:longHIGH];
49    waveLongLow = [self generateHalfSine:false halfPeriod:longLOW];
50    longLowLength = [self arraySizeWithHalfPeriod:longLOW];
51    waveShortHigh = [self generateHalfSine:true halfPeriod:shortHIGH];
52    shortHighLength = [self arraySizeWithHalfPeriod:shortHIGH];
53    waveShortLow = [self generateHalfSine:false halfPeriod:shortLOW];
54    shortLowLength = [self arraySizeWithHalfPeriod:shortLOW];
55   
56    float mediumLow = 0.0005 - sampleTime;
57    waveMediumLow = [self generateHalfSine:false halfPeriod:mediumLow];
58    mediumLowLength = [self arraySizeWithHalfPeriod:mediumLow];
59   
60   
61    longZeroLength = (int)floor((0.002 + 1 / sampleRate) * sampleRate);
62    waveLongZero = malloc(longZeroLength);
63   
64    [self generateWaveBit];
65}
66
67- (void) generateWaveBit
68{
69    waveBitLength = longHighLength + longLowLength + shortHighLength + shortLowLength;
70    waveBit = malloc(sizeof(waveBit) * waveBitLength);
71    int c = 0;
72    for (int i = 0; i < longHighLength; i++) {
73        waveBit[c++] = waveLongHigh[i];
74    }
75    for (int i = 0; i < longLowLength; i++) {
76        waveBit[c++] = waveLongLow[i];
77    }
78    for (int i = 0; i < shortHighLength; i++) {
79        waveBit[c++] = waveShortHigh[i];
80    }
81    for (int i = 0; i < shortLowLength; i++) {
82        waveBit[c++] = waveShortLow[i];
83    }
84}
85
86
87- (void) stop
88{
89    if (toneUnit != nil) {
90        AudioOutputUnitStop(toneUnit);
91        AudioUnitUninitialize(toneUnit);
92        AudioComponentInstanceDispose(toneUnit);
93        toneUnit = nil;
94    }
95}
96
97- (void) playWithThrottle: (int)t yaw: (int)y pitch: (int)p
98{
99    self.yaw = y;
100    self.pitch = p;
101    self.throttle = t;
102   
103    if (toneUnit == nil) {
104        [self createToneUnit];
105       
106        // Stop changing parameters on the unit
107        OSErr err = AudioUnitInitialize(toneUnit);
108       
109        // Start playback
110        err = AudioOutputUnitStart(toneUnit);
111    }
112}
113
114OSStatus RenderTone(
115                    void *inRefCon,
116                    AudioUnitRenderActionFlags  *ioActionFlags,
117                    const AudioTimeStamp                *inTimeStamp,
118                    UInt32                                              inBusNumber,
119                    UInt32                                              inNumberFrames,
120                    AudioBufferList                     *ioData)
121
122{
123   
124       
125//      // This is a mono tone generator so we only need the first buffer
126//      const int channel = 0;
127   
128   // There are two channels to the audio control stream (left and right)
129   const int channel = 0; // Output to left channel
130//   const int channel = 1; // Output to right channel
131
132   
133   
134        Float32 *buffer = (Float32 *)ioData->mBuffers[channel].mData;
135   
136    AudioGenerator *generator = (__bridge AudioGenerator *)inRefCon;
137    [generator writeBytesToBuffer:buffer maxSize:inNumberFrames];
138    return noErr;
139}
140
141/**
142 * All this code stolen from http://www.cocoawithlove.com/2010/10/ios-tone-generator-introduction-to.html
143 */
144- (void) createToneUnit
145{
146    // Configure the search parameters to find the default playback output unit
147    // (called the kAudioUnitSubType_RemoteIO on iOS but
148    // kAudioUnitSubType_DefaultOutput on Mac OS X)
149    AudioComponentDescription defaultOutputDescription;
150    defaultOutputDescription.componentType = kAudioUnitType_Output;
151    defaultOutputDescription.componentSubType = kAudioUnitSubType_RemoteIO;
152    defaultOutputDescription.componentManufacturer = kAudioUnitManufacturer_Apple;
153    defaultOutputDescription.componentFlags = 0;
154    defaultOutputDescription.componentFlagsMask = 0;
155   
156    // Get the default playback output unit
157    AudioComponent defaultOutput = AudioComponentFindNext(NULL, &defaultOutputDescription);
158    if (defaultOutput == NULL) {
159        NSLog(@"Can't find default output");
160    }
161   
162    // Create a new unit based on this that we'll use for output
163    OSErr err = AudioComponentInstanceNew(defaultOutput, &toneUnit);
164    if (toneUnit == NULL) {
165        NSLog(@"Error creating unit: %hd", err);
166    }
167   
168    // Set our tone rendering function on the unit
169    AURenderCallbackStruct input;
170    input.inputProc = RenderTone;
171    input.inputProcRefCon = (__bridge void *)(self);
172    err = AudioUnitSetProperty(toneUnit,
173                               kAudioUnitProperty_SetRenderCallback,
174                               kAudioUnitScope_Input,
175                               0,
176                               &input,
177                               sizeof(input));
178    if (err != noErr) {
179        NSLog(@"Error setting callback: %hd", err);
180    }
181   
182    // Set the format to 32 bit, single channel, floating point, linear PCM
183    const int four_bytes_per_float = 4;
184    const int eight_bits_per_byte = 8;
185    AudioStreamBasicDescription streamFormat;
186    streamFormat.mSampleRate = sampleRate;
187    streamFormat.mFormatID = kAudioFormatLinearPCM;
188    streamFormat.mFormatFlags =
189    kAudioFormatFlagsNativeFloatPacked | kAudioFormatFlagIsNonInterleaved;
190    streamFormat.mBytesPerPacket = four_bytes_per_float;
191    streamFormat.mFramesPerPacket = 1;
192    streamFormat.mBytesPerFrame = four_bytes_per_float;
193   
194   // There are two channels to the audio stream
195//   streamFormat.mChannelsPerFrame = 1;
196   streamFormat.mChannelsPerFrame = 2;
197   
198    streamFormat.mBitsPerChannel = four_bytes_per_float * eight_bits_per_byte;
199    err = AudioUnitSetProperty (toneUnit,
200                                kAudioUnitProperty_StreamFormat,
201                                kAudioUnitScope_Input,
202                                0,
203                                &streamFormat,
204                                sizeof(AudioStreamBasicDescription));
205    if (err != noErr) {
206        NSLog(@"Error setting stream format: %hd", err);
207    }
208}
209
210// calculate the checksum for the generated code used to generate the WAV array
211- (int) codeChecksum:(int)code
212{
213    int checksum = 0;
214    for (int i = 0; i < 7; i++) {
215        checksum += (code >> 4*i) & 15;
216    }
217    return 16 - (checksum & 15);
218}
219
220// Generate the code used to create the WAV file based on the given throttle, yaw and pitch.
221// Copied from AudioService.java in the Android app (command2code method)
222// throttle: 0~127, nothing will happen if this value is below 30.
223// yaw: 0~127, normally 78 will keep orbit from rotating.
224// pitch: 0~63, normally 31 will stop the top propeller.
225// channel: 1=Channel A, 0=Channel B 2= Channel C, depend on which channel you want to pair to the orbit. You can fly at most 3 orbit in a same room.
226- (int) generateCode
227{
228    int channel = 1;
229    int code = throttle << 21;
230    code += 1048576; // code += 1 << 20 ;
231    code += yaw << 12;
232    code += pitch << 4 ;
233    code += ((channel >> 1) & 1) << 19;
234    code += (channel & 1) << 11;
235    return code + [self codeChecksum:code];
236}
237
238- (void) writeBytesToBuffer:(Float32 *) buffer maxSize:(UInt32) frames
239{
240    MAX_BUFFER_SIZE = sizeof(buffer) * frames;
241    int position = 0;
242    [self writeWaveToBuffer:buffer        at:&position];
243    [self writeInitialWaveToBuffer:buffer at:&position];
244}
245
246- (void) writeInitialWaveToBuffer:(Float32 *) buffer at: (int *) position
247{
248    [self writeWave123To: buffer at: position];
249    [self writeWave123To: buffer at: position];
250    [self writeWave456To: buffer at: position];
251    [self writeWave456To: buffer at: position];
252    [self writeWave456To: buffer at: position];
253}
254
255- (void) writeWave123To:(Float32 *) buffer at:(int *) position
256{
257    [self writeOriginalTo:buffer at: position];
258    [self writeArray:waveMediumLow to:buffer length:mediumLowLength at:position];
259    for (int i = 0; i < 4; i++) {
260        [self writeShortHShortLTo:buffer at:position];
261    }
262    [self writeArray:waveShortHigh to:buffer length:shortHighLength at:position];
263    [self writeMediumLShortHTo:buffer at:position];
264    [self writePauseInSamplesTo:buffer at:position];
265}
266
267- (void) writeWave456To:(Float32 *) buffer at:(int *) position
268{
269    [self writeOriginalTo:buffer at:position];
270    [self writeArray:waveShortLow to:buffer length:shortLowLength at:position];
271    for (int i = 0; i < 4; i++) {
272        [self writeShortHShortLTo:buffer at:position];
273    }
274    [self writeArray:waveShortHigh to:buffer length:shortHighLength at:position];
275    [self writeMediumLShortHTo:buffer at:position];
276    [self writePauseInSamplesTo:buffer at:position];
277}
278
279
280- (void) writeOriginalTo:(Float32 *) buffer at:(int *) position
281{
282    [self writeLongHLongZeroTo: buffer at: position];
283    [self writeLongHLongZeroTo: buffer at: position];
284    [self writeArray:waveLongHigh to:buffer length:longHighLength at:position];
285    [self writeMediumLShortHTo:buffer at:position];
286}
287
288- (void) writeLongHLongZeroTo:(Float32 *) buffer at:(int *) position
289{
290    [self writeArray:waveLongHigh to:buffer length:longHighLength at:position];
291    [self writeArray:waveLongZero to:buffer length:longZeroLength at:position];
292}
293
294- (void) writeMediumLShortHTo:(Float32 *) buffer at:(int *) position
295{
296    [self writeArray:waveMediumLow to:buffer length:mediumLowLength at:position];
297    [self writeArray:waveShortHigh to:buffer length:shortHighLength at:position];
298}
299
300- (void) writeShortHShortLTo:(Float32 *) buffer at:(int *) position
301{
302    [self writeArray:waveShortHigh to:buffer length:shortHighLength at:position];
303    [self writeArray:waveShortLow to:buffer length:shortLowLength at:position];
304}
305
306- (void) writePauseInSamplesTo:(Float32 *) buffer at:(int *) position
307{
308    int length = (int)floor(0.010 * sampleRate);
309    float arr[length];
310    [self writeArray:arr to:buffer length:length at:position];
311}
312
313- (void) writeArray:(float *) array to:(Float32 *) buffer length: (int)length at:(int *) position
314{
315    for (int i = 0; i < length; i++) {
316        if (*position < MAX_BUFFER_SIZE) buffer[(*position)++] = array[i];
317    }
318}
319
320- (void) writeWaveToBuffer:(Float32 *) buffer at: (int *) position
321{
322    [self halfSineGen: false halfPeriod: longLOW                   toBuffer:buffer at:position];
323    [self halfSineGen: true  halfPeriod: longHIGH - sampleTime * 2 toBuffer:buffer at:position];
324    [self halfSineGen: false halfPeriod: shortLOW + sampleTime * 2 toBuffer:buffer at:position];
325    [self halfSineGen: true  halfPeriod: longHIGH - sampleTime * 2 toBuffer:buffer at:position]; // duplicate?
326    [self halfSineGen: false halfPeriod: shortLOW + sampleTime * 2 toBuffer:buffer at:position]; // duplicate?
327   
328    int code = [self generateCode];
329    for (int i=0; i < 27; i++) {
330        buffer[(*position)++] = waveBit[((code >> (27 - i)) & 1)];
331    }
332    for (int i = 0; i < longHighLength; i++) {
333        buffer[(*position)++] = waveLongHigh[i];
334    }
335   
336}
337
338/**
339 * Generate half sine signal.
340 * Copied from AudioService.java in the AndroidApp
341 * @param upper, means it's the upper half or lower half or sine wave.
342 * @param halfPeriod: half of the period of sine wave, in seconds
343 * @param toBuffer the buffer (float array) to write to
344 * @param position the position in the array to start writing to
345 */
346- (void) halfSineGen:(BOOL)upper halfPeriod: (double)halfPeriod toBuffer:(Float32 *) buffer at:(int *) position
347{
348    int sampleCount = [self arraySizeWithHalfPeriod:halfPeriod];
349    double increment = M_PI/(halfPeriod * sampleRate);
350    double angle = upper ? 0 : M_PI;
351       
352    for (int i = 0; i < sampleCount; i++) {
353        buffer[(*position)++] = sinf(angle);
354        angle += increment;
355    }
356}
357
358- (int) arraySizeWithHalfPeriod: (double)halfPeriod
359{
360    return (int)floor(halfPeriod * sampleRate);
361}
362
363/**
364 * Generate half sine signal.
365 * Copied from AudioService.java in the AndroidApp
366 * @param upper, means it's the upper half or lower half of sine wave.
367 * @param halfPeriod: half of the period of sine wave, in seconds
368 * @return the length of the array
369 */
370- (float *) generateHalfSine:(BOOL)upper halfPeriod: (double)halfPeriod
371{
372    int sampleCount = [self arraySizeWithHalfPeriod:halfPeriod];
373    float *array = malloc(sizeof(*array) * sampleCount);
374    double increment = M_PI/(halfPeriod * sampleRate);
375    double angle = upper ? 0 : M_PI;
376   
377    for (int i = 0; i < sampleCount; i++) {
378        array[i] = sinf(angle);
379        angle += increment;
380    }
381    return array;
382}
383
384@end
Note: See TracBrowser for help on using the repository browser.