Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Save MervinPraison/3c0c67b56ebfaf9d284b3e1000757857 to your computer and use it in GitHub Desktop.
Save MervinPraison/3c0c67b56ebfaf9d284b3e1000757857 to your computer and use it in GitHub Desktop.
Find Guitar notes using an Iterator

Mapping Notes on a Guitar Fretboard

This gist has the source code to match the series of Blog posts for mapping notes on guitar.

Here is a brief rundown of the source files, and headers in this gist:

TestIteration.m                    - example code for using the Iterator


MusicGuitarStringFretIterator.hm   - Offers iteration over Tab staff / guitar fretboard
MusicGuitarStringFretLocation.hm   - basic string-fret location class
MusicGuitarStringFretRange.hm      - defines range for controlling iterators fretboard position
MusicGuitarTabSixStaff.hm          - actual TAB staff, has six (or four) tuneable strings

MusicTabStaffStringFretMidiMaps.hm - acts as the 'string' on TAB staff and holds
                                     a series of SingleFretMap's
MusicTabStaffSingleFretMap.hm      - contains info for an individual guitar fret

MidiNoteFrequenciesPianoTable.hm   - collection class, opens, parses .plist, offers lookups
MidiNoteFrequencyEntry.hm          - one element in the midi lookup table collection
MusicMidiNoteOctave.hm             - simple class, part of public Api to Iterator find methods
MidiCodeFrequencies.plist          - plist file defines 4 arrays, notes, midi codes, etc.

Since this is a gist, these files will not compile as is. They are part of a much larger project.

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>MidiCode</key>
<array>
<integer>21</integer>
<integer>22</integer>
<integer>23</integer>
<integer>24</integer>
<integer>25</integer>
<integer>26</integer>
<integer>27</integer>
<integer>28</integer>
<integer>29</integer>
<integer>30</integer>
<integer>31</integer>
<integer>32</integer>
<integer>33</integer>
<integer>34</integer>
<integer>35</integer>
<integer>36</integer>
<integer>37</integer>
<integer>38</integer>
<integer>39</integer>
<integer>40</integer>
<integer>41</integer>
<integer>42</integer>
<integer>43</integer>
<integer>44</integer>
<integer>45</integer>
<integer>46</integer>
<integer>47</integer>
<integer>48</integer>
<integer>49</integer>
<integer>50</integer>
<integer>51</integer>
<integer>52</integer>
<integer>53</integer>
<integer>54</integer>
<integer>55</integer>
<integer>56</integer>
<integer>57</integer>
<integer>58</integer>
<integer>59</integer>
<integer>60</integer>
<integer>61</integer>
<integer>62</integer>
<integer>63</integer>
<integer>64</integer>
<integer>65</integer>
<integer>66</integer>
<integer>67</integer>
<integer>68</integer>
<integer>69</integer>
<integer>70</integer>
<integer>71</integer>
<integer>72</integer>
<integer>73</integer>
<integer>74</integer>
<integer>75</integer>
<integer>76</integer>
<integer>77</integer>
<integer>78</integer>
<integer>79</integer>
<integer>80</integer>
<integer>81</integer>
<integer>82</integer>
<integer>83</integer>
<integer>84</integer>
<integer>85</integer>
<integer>86</integer>
<integer>87</integer>
<integer>88</integer>
<integer>89</integer>
<integer>90</integer>
<integer>91</integer>
<integer>92</integer>
<integer>93</integer>
<integer>94</integer>
<integer>95</integer>
<integer>96</integer>
<integer>97</integer>
<integer>98</integer>
<integer>99</integer>
<integer>100</integer>
<integer>101</integer>
<integer>102</integer>
<integer>103</integer>
<integer>104</integer>
<integer>105</integer>
<integer>106</integer>
<integer>107</integer>
<integer>108</integer>
</array>
<key>NoteName</key>
<array>
<string>A0</string>
<string>Black</string>
<string>B0</string>
<string>C0</string>
<string>Black</string>
<string>D1</string>
<string>Black</string>
<string>E1</string>
<string>F1</string>
<string>Black</string>
<string>G1</string>
<string>Black</string>
<string>A1</string>
<string>Black</string>
<string>B1</string>
<string>C2</string>
<string>Black</string>
<string>D2</string>
<string>Black</string>
<string>E2</string>
<string>F2</string>
<string>Black</string>
<string>G2</string>
<string>Black</string>
<string>A2</string>
<string>Black</string>
<string>B2</string>
<string>C3</string>
<string>Black</string>
<string>D3</string>
<string>Black</string>
<string>E3</string>
<string>F3</string>
<string>Black</string>
<string>G3</string>
<string>Black</string>
<string>A3</string>
<string>Black</string>
<string>B3</string>
<string>C4</string>
<string>Black</string>
<string>D4</string>
<string>Black</string>
<string>E4</string>
<string>F4</string>
<string>Black</string>
<string>G4</string>
<string>Black</string>
<string>A4</string>
<string>Black</string>
<string>B4</string>
<string>C5</string>
<string>Black</string>
<string>D5</string>
<string>Black</string>
<string>E5</string>
<string>F5</string>
<string>Black</string>
<string>G5</string>
<string>Black</string>
<string>A5</string>
<string>Black</string>
<string>B5</string>
<string>C6</string>
<string>Black</string>
<string>D6</string>
<string>Black</string>
<string>E6</string>
<string>F6</string>
<string>Black</string>
<string>G6</string>
<string>Black</string>
<string>A6</string>
<string>Black</string>
<string>B6</string>
<string>C7</string>
<string>Black</string>
<string>D7</string>
<string>Black</string>
<string>E7</string>
<string>F7</string>
<string>Black</string>
<string>G7</string>
<string>Black</string>
<string>A7</string>
<string>Black</string>
<string>B7</string>
<string>C8</string>
</array>
<key>Frequency</key>
<array>
<real>27.5</real>
<real>29.135</real>
<real>30.868</real>
<real>32.703</real>
<real>34.648</real>
<real>36.708</real>
<real>38.891</real>
<real>41.203</real>
<real>43.654</real>
<real>46.249</real>
<real>48.999</real>
<real>51.913</real>
<real>55</real>
<real>58.27</real>
<real>61.735</real>
<real>65.40600000000001</real>
<real>69.29600000000001</real>
<real>73.416</real>
<real>77.782</real>
<real>82.407</real>
<real>87.307</real>
<real>92.499</real>
<real>97.999</real>
<real>103.83</real>
<real>110</real>
<real>116.54</real>
<real>123.47</real>
<real>130.81</real>
<real>138.59</real>
<real>146.83</real>
<real>155.56</real>
<real>164.81</real>
<real>174.61</real>
<real>185</real>
<integer>196</integer>
<real>207.65</real>
<integer>220</integer>
<real>233.08</real>
<real>246.94</real>
<real>261.63</real>
<real>277.18</real>
<real>293.67</real>
<real>311.13</real>
<real>329.63</real>
<real>349.23</real>
<real>369.99</real>
<real>392</real>
<real>415.3</real>
<integer>440</integer>
<real>466.16</real>
<real>493.88</real>
<real>523.25</real>
<real>554.37</real>
<real>587.33</real>
<real>622.25</real>
<real>659.26</real>
<real>698.46</real>
<real>739.99</real>
<real>783.99</real>
<real>830.61</real>
<integer>880</integer>
<real>932.33</real>
<real>987.77</real>
<real>1046.5</real>
<real>1108.7</real>
<real>1174.7</real>
<real>1244.5</real>
<real>1318.5</real>
<real>1396.9</real>
<real>1480</real>
<real>1568</real>
<real>1661.2</real>
<integer>1760</integer>
<real>1864.7</real>
<real>1975.5</real>
<integer>2093</integer>
<real>2217.5</real>
<real>2349.3</real>
<real>2489</real>
<integer>2637</integer>
<real>2793</real>
<integer>2960</integer>
<real>3136</real>
<real>3322.4</real>
<integer>3520</integer>
<real>3729.3</real>
<real>3951.1</real>
<real>4186</real>
</array>
<key>Period</key>
<array>
<real>36.36</real>
<real>34.32</real>
<real>32.4</real>
<real>30.58</real>
<real>28.86</real>
<real>27.24</real>
<real>25.71</real>
<real>24.27</real>
<real>22.91</real>
<real>21.62</real>
<real>20.41</real>
<real>19.26</real>
<real>18.18</real>
<real>17.16</real>
<real>16.2</real>
<real>15.29</real>
<real>14.29</real>
<real>13.62</real>
<real>12.86</real>
<real>12.13</real>
<real>11.45</real>
<real>10.81</real>
<real>10.2</real>
<real>9.631</real>
<real>9.090999999999999</real>
<real>8.581</real>
<real>8.099</real>
<real>7.645</real>
<real>7.216</real>
<real>6.811</real>
<real>6.428</real>
<real>6.068</real>
<real>5.727</real>
<real>5.405</real>
<real>5.102</real>
<real>4.816</real>
<real>4.545</real>
<real>4.29</real>
<real>4.05</real>
<real>3.822</real>
<real>3.608</real>
<real>3.405</real>
<real>3.214</real>
<real>3.034</real>
<real>2.863</real>
<real>2.703</real>
<real>2.551</real>
<real>2.408</real>
<real>2.273</real>
<real>2.145</real>
<real>2.025</real>
<real>1.91</real>
<real>1.804</real>
<real>1.703</real>
<real>1.607</real>
<real>1.517</real>
<real>1.432</real>
<real>1.351</real>
<real>1.276</real>
<real>1.204</real>
<real>1.136</real>
<real>1.073</real>
<real>1.012</real>
<real>0.9556</real>
<real>0.902</real>
<real>0.8512999999999999</real>
<real>0.8034</real>
<real>0.7584</real>
<real>0.7159</real>
<real>0.6757</real>
<real>0.6378</real>
<real>0.602</real>
<real>0.5682</real>
<real>0.5363</real>
<real>0.5062</real>
<real>0.4778</real>
<real>0.451</real>
<real>0.4257</real>
<real>0.4018</real>
<real>0.3792</real>
<real>0.358</real>
<real>0.3378</real>
<real>0.3189</real>
<real>0.301</real>
<real>0.2841</real>
<real>0.2681</real>
<real>0.2531</real>
<real>0.2389</real>
</array>
</dict>
</plist>
//
// MusicGuitarStringFretLocation.h
// MusicTheory
//
// Created by Michael J Albanese on 4/24/14.
// Copyright (c) 2014 Michael J Albanese. All rights reserved.
//
#import <Foundation/Foundation.h>
#import "MusicTheoryDefs.h"
/**
* Identifies a string and fret number on a guitar fretboard, nothing else !
*/
@interface MusicGuitarStringFretLocation : NSObject
@property (nonatomic) kMusicGuitarString stringNumber;
@property (nonatomic) NSInteger fretNumber;
+ (instancetype) locationWithString:(kMusicGuitarString)string andFret:(NSInteger)fret;
@end
//
// MusicGuitarStringFretLocation.m
// MusicTheory
//
// Created by Michael J Albanese on 4/24/14.
// Copyright (c) 2014 Michael J Albanese. All rights reserved.
//
#import "MusicGuitarStringFretLocation.h"
@implementation MusicGuitarStringFretLocation
+ (instancetype) locationWithString:(kMusicGuitarString)string andFret:(NSInteger)fret
{
return [[MusicGuitarStringFretLocation alloc] initWithString:string andFret:fret];
}
- (id) initWithString:(kMusicGuitarString)string andFret:(NSInteger)fret
{
if (self = [super init]) {
_stringNumber = kMusicGuitarString_None;
if (kVALID_GUITAR_STRING(string)) {
_stringNumber = string;
}
_fretNumber = fret;
}
return self;
}
@end
//
// MusicGuitarStringFretRange.h
// MusicTheory
//
// Created by Michael J Albanese on 4/24/14.
// Copyright (c) 2014 Michael J Albanese. All rights reserved.
//
#import <Foundation/Foundation.h>
#import "MusicGuitarStringFretLocation.h"
/**
* This range class can be handed to a MusicGuitarStringFretIterator supplying it the
* parameters within which to conduct its searching/iterating. The concept of 'bottom'
* string refers to the bottom most string on the instrument, that being
* (6th on guitar, or 4th on bass). The consumer of this range object will cease to
* operate reliably if the values for bottom represent a location on the fretboard
* that is equal to or beyond what is given for the topStringFret.
*
* There is the concept of a 'middle string fret'. This would be the starting point
* for an iterator to being searching for a midi-code or note on the fretboard. Loosely
* analagous to middle C on a piano. If not supplied (is it optional) then the iterator
* simply starts at the bottom String Fret in its search.
*
* Another concept is the 'interimUpperFretLimit'. This can be a different fret number than
* given in the 'topStringFret', and would confine the iterator to that fret number when
* searching on all strings between the bottom and top string. If unset (e.g. value == 0)
* then the iterator will walk up to the fret number named in the topStringFret while
* iterating over frets on iterim strings.
*
* Note: By default the interim Upper and Bottom limits get set to a value -1. When
* the Iterator sees this it looks into the bottom and top Location properties
* for guidance in setting the lower/upper bounds on fret searches on iterim strings.
* 'Interim' strings are those strings between the bottom and top StringLocations.
*
* @seealso MusicGuitarStringFretIterator
*/
@interface MusicGuitarStringFretRange : NSObject
@property (strong, nonatomic) MusicGuitarStringFretLocation *bottomStringLocation;
@property (strong, nonatomic) MusicGuitarStringFretLocation *topStringLocation;
@property (strong, nonatomic) MusicGuitarStringFretLocation *middleStringLocation;
@property (nonatomic) NSInteger interimUpperFretLimit;
@property (nonatomic) NSInteger interimBottomFretLimit;
/**
* Class creator builds a range object using the given 'bottom' 'top' locations which
* define lower and upper fretboard limits, typically given to the Iterator to confine
* the seach for notes on a guitar. The 'middleLocation' is optional.
*
* @param bottomLocation defines a lower bound on the guitar, limiting the iterator
* @param topLocation defines upper bound on guitar, limiting the iterator
* @param middleLocation (optional) defines some point which may be used to start a search
*/
+ (instancetype) stringFretRangeUsingBottom:(MusicGuitarStringFretLocation *)bottomLocation
andTopLocation:(MusicGuitarStringFretLocation *)topLocation
andMiddleLocation:(MusicGuitarStringFretLocation *)middleLocation;
@end
//
// MusicGuitarStringFretRange.m
// MusicTheory
//
// Created by Michael J Albanese on 4/24/14.
// Copyright (c) 2014 Michael J Albanese. All rights reserved.
//
#import "MusicGuitarStringFretRange.h"
@implementation MusicGuitarStringFretRange
+ (instancetype) stringFretRangeUsingBottom:(MusicGuitarStringFretLocation *)bottomLocation
andTopLocation:(MusicGuitarStringFretLocation *)topLocation
andMiddleLocation:(MusicGuitarStringFretLocation *)middleLocation
{
MusicGuitarStringFretRange *theRange = [[MusicGuitarStringFretRange alloc] init];
theRange.bottomStringLocation = bottomLocation;
theRange.topStringLocation = topLocation;
theRange.middleStringLocation = middleLocation;
return theRange;
}
- (id) init
{
if (self = [super init]) {
_interimBottomFretLimit = -1;
_interimUpperFretLimit = -1;
}
return self;
}
@end
//
// MusicGuitarTabSixStaff.h
// MusicRendering
//
// Created by Michael J Albanese on 4/9/14.
// Copyright (c) 2014 Michael J Albanese. All rights reserved.
//
#import "MusicStaff.h"
#import "MusicStaffTraversable_p.h"
@class MusicTabClefRenderable;
@class MusicMidiNoteFrequenciesPianoTable;
/**
* Derived from the standard MusicStaff class. Important that the 'init' method
* be defined publicly, AND express the _exact same_ prototype as the base class.
* This is due to the nature of having a MusicStaffFactory object be the central
* instantiater of staffs (although public may also create staffs stand alone).
*/
@interface MusicGuitarTabSixStaff : MusicStaff <MusicStaffTraversable_p>
+ (MusicGuitarTabSixStaff *) guitarTabStaffWithLayoutInfo:(MusicStaffLayoutInfo *)layoutInfo
andMiddleXCoordinate:(float)middleX
andLowestYCoordinate:(float)lowY
andStaffMetrics:(MusicStaffRenderMetrics *)staffMetrics
andPageRenderCriteria:(MusicPageRenderCriteria *)renderCriteria;
- (id) initWithLayoutInfo:(MusicStaffLayoutInfo *)layoutInfo
andMiddleXCoordinate:(float)middleX
andLowestYCoordinate:(float)lowY
andStaffMetrics:(MusicStaffRenderMetrics *)staffMetrics
andPageRenderCriteria:(MusicPageRenderCriteria *)renderCriteria;
- (void) tuneStringsUsingBaseMidiCodes:(NSArray *)arrayOfMidiCodes;
- (MusicMidiNoteFrequenciesPianoTable *) getMidiCodeTable;
- (void) clearMidiTable;
- (void) calcClefDrawingRectsWithLeftMostX:(CGFloat)leftMostX topMostY:(CGFloat)topMostY
areaSize:(CGSize)areaSize andFont:(UIFont *)theFont
intoRenderable:(MusicTabClefRenderable *) tabClefRenderable;
/** quasi 'overrides' of base/super methods */
- (void) mapStaffOctaveAndNoteCoordinates;
- (void) calcDropZonesFromCombinedVectorMappings;
@end
//
// MusicGuitarTabSixStaff.m
// MusicRendering
//
// Created by Michael J Albanese on 4/9/14.
// Copyright (c) 2014 Michael J Albanese. All rights reserved.
//
#import <CoreText/CoreText.h>
#import "SWFVector3.h"
#import "NoteData+MoreFuncs.h"
#import "MusicStaffNotifications_p.h"
#import "MusicPageRenderCriteria.h"
#import "MusicStaffRenderMetrics.h"
#import "MusicTabStaffStringFretMidiMaps.h"
#import "MusicMidiNoteFrequenciesPianoTable.h"
#import "MusicMidiNoteFrequencyEntry.h"
#import "MusicTabClefRenderable.h"
#import "MusicStaffLineVectorMap.h"
#import "MusicStaffDropZone.h"
#import "MusicGuitarTabSixStaff.h"
#pragma mark - TabSixStaff Class
@interface MusicGuitarTabSixStaff ()
@property (strong, nonatomic) MusicMidiNoteFrequenciesPianoTable *midiTable;
@property (strong, nonatomic) MusicTabClefRenderable *tabClefRenderable;
@end
#pragma mark - Main TabSixStaff Class Methods
@implementation MusicGuitarTabSixStaff
+ (MusicGuitarTabSixStaff *) guitarTabStaffWithLayoutInfo:(MusicStaffLayoutInfo *)layoutInfo
andMiddleXCoordinate:(float)middleX
andLowestYCoordinate:(float)lowY
andStaffMetrics:(MusicStaffRenderMetrics *)staffMetrics
andPageRenderCriteria:(MusicPageRenderCriteria *)pageCriteria
{
return [[MusicGuitarTabSixStaff alloc] initWithLayoutInfo:layoutInfo
andMiddleXCoordinate:middleX
andLowestYCoordinate:lowY
andStaffMetrics:staffMetrics
andPageRenderCriteria:pageCriteria];
}
- (id) initWithLayoutInfo:(MusicStaffLayoutInfo *)layoutInfo
andMiddleXCoordinate:(float)middleX
andLowestYCoordinate:(float)lowY
andStaffMetrics:(MusicStaffRenderMetrics *)staffMetrics
andPageRenderCriteria:(MusicPageRenderCriteria *)pageCriteria
{
if (self = [super initWithLayoutInfo:layoutInfo
andMiddleXCoordinate:middleX
andLowestYCoordinate:lowY
andStaffMetrics:staffMetrics
andPageRenderCriteria:pageCriteria]) {
_tabClefRenderable = [[MusicTabClefRenderable alloc] init];
}
return self;
}
- (kSongStaffId) staffId
{
return self.staffLayoutInfo.staffId;
}
// FIXME either make this Class static member (of GuitarTabSixStaff)
// - OR - make the MidiTable itself a Singleton
//
// Currently a static member of the MusicKey hierarchy's base class...
//
- (MusicMidiNoteFrequenciesPianoTable *) getMidiCodeTable
{
if (_midiTable == nil) {
_midiTable = [MusicMidiNoteFrequenciesPianoTable midiNoteFrequenciesPianoTable];
[_midiTable buildFrequenciesTable];
}
return _midiTable;
}
- (void) clearMidiTable
{
_midiTable = nil;
}
#pragma mark - Mapping Staff Strings
- (void) mapStaffOctaveAndNoteCoordinates
{
NSMutableArray *arMidiCodes = [NSMutableArray array];
// Note the use of the staff layout 'usedStaffLines' property to control
// number of nut midi codes (may be 6 or 4). Implicitily this dictates
// line count for either a six string guitar, or four string bass guitar.
for (int i = 0; i < self.staffLayoutInfo.usedStaffLines; i++) {
NSInteger nutMidiCode = self.staffLayoutInfo.lineNotes[i].midiCode;
[arMidiCodes addObject:@(nutMidiCode)];
}
if (arMidiCodes.count > 0) {
[self tuneStringsUsingBaseMidiCodes:arMidiCodes];
}
}
// Consider making this a public method, or in some way callable
// in order to retune a Tab staff
- (void) tuneStringsUsingBaseMidiCodes:(NSArray *)arrayOfMidiCodes
{
[self.staffVectorMappings removeAllObjects];
[self.linesVectorMappingsIndex removeAllObjects];
[self.loToHiCombinedMappingsIndex removeAllObjects];
float x = self.middleXCoordinate;
float y = self.lowestYCoordinate;
float z = 0.;
self.lowestLineVectorMapIndex = self.highestLineVectorMapIndex = kMaxStaffLines + 10;
self.highestYCoordinate = 2000.0;
float yincr = self.staffMetrics.yGapBetweenLines;
MusicMidiNoteFrequenciesPianoTable *midiTable = [self getMidiCodeTable];
MusicTabStaffStringFretMidiMaps *oneStringMapping;
// self.staffLayoutInfo.usedStaffLines;
if (arrayOfMidiCodes.count != kMusicGuitarTotalStrings) {
NSAssert1(arrayOfMidiCodes.count == kMusicGuitarTotalStrings,
@"Illegal string count for TAB staff: %d", arrayOfMidiCodes.count);
}
// utilize stringNumber Enums which are zero based, 0 being 6th string
NSInteger stringNumber = kMusicGuitarFirstString;
// loop bottom up for Lines in staff
while (stringNumber <= kMusicGuitarLastString) {
NSNumber *codeNumber = arrayOfMidiCodes[stringNumber];
NSInteger nutMidiCode = codeNumber.integerValue;
oneStringMapping = [MusicTabStaffStringFretMidiMaps stringFretMidiMapForStringNumber:stringNumber];
oneStringMapping.vec = [SWFVector3 vectorFromValues:x y:y z:z];
oneStringMapping.zoneType = kMusicLineZone;
[oneStringMapping tuneAllFretsUsingNutMidiCode:nutMidiCode
andFrequenciesTable:midiTable];
// Add the custom StringFretMidiMap object into the inherited vectorMappings array
[self.staffVectorMappings addObject:oneStringMapping];
if (stringNumber == kMusicGuitarFirstString) {
self.lowestLineVector = [SWFVector3 vectorFromVector:oneStringMapping.vec];
self.lowestLineVectorMapIndex = stringNumber;
}
if (y < self.highestYCoordinate) {
self.highestYCoordinate = y;
self.highestLineVectorMapIndex = stringNumber;
}
// update the lines index with offset of newly added line mapping
[self.linesVectorMappingsIndex addObject:@(self.staffVectorMappings.count - 1)];
y -= yincr;
stringNumber++;
}
// now populate the combined mappings array even though this staff only has lines
[self.loToHiCombinedMappingsIndex removeAllObjects];
NSInteger totalMappings = self.staffVectorMappings.count;
NSInteger lineOff = 0;
for (NSInteger x = 0; x < totalMappings; x++) {
if (lineOff < self.linesVectorMappingsIndex.count) {
oneStringMapping = self.staffVectorMappings[lineOff];
if (oneStringMapping) {
[self.loToHiCombinedMappingsIndex addObject:@(lineOff)];
}
lineOff++;
}
}
[self clearMidiTable];
}
- (SWFVector3 *) findStaffVectorForNoteUsingItsGuitarString:(NoteData *)aNote
{
SWFVector3 *noteVec;
MusicTabStaffStringFretMidiMaps *oneStringMapping;
if (aNote.guitarStringNumber) {
NSInteger zeroBasedStringOffset = aNote.guitarStringNumber.integerValue;
if (kVALID_GUITAR_STRING(zeroBasedStringOffset) &&
zeroBasedStringOffset < self.staffVectorMappings.count) {
oneStringMapping = self.staffVectorMappings[ zeroBasedStringOffset ];
noteVec = [oneStringMapping.vec mutableCopy];
}
}
return noteVec;
}
#pragma mark - MusicStaffDisplayable Protocol methods
- (SWFVector3 *) findStaffVectorForNoteUsingItsOctave:(NoteData *)aNote
{
SWFVector3 *noteVec = [self findStaffVectorForNoteUsingItsGuitarString:aNote];
return noteVec;
}
- (NSArray *) measureBarCoordinatesForLine
{
return [super measureBarCoordinatesForLine];
}
- (float) calcTopRealLineUsingMetrics
{
CGFloat fixedStaffHeight = (self.staffMetrics.fixedLineCount - 1) * self.staffMetrics.yGapBetweenLines;
fixedStaffHeight += self.staffMetrics.fixedLineCount * self.staffMetrics.staffLineWidth;
CGFloat firstY = self.staffYCenter - (fixedStaffHeight / 2);
if (self.staffMetrics.staffLineWidth == 1) {
firstY = [MusicStaffRenderMetrics roundToLeastHalfPoint:firstY];
} else if (self.staffMetrics.staffLineWidth == 2) {
firstY = floorf(firstY);
}
return firstY;
}
- (float) calcBottomRealLineUsingMetrics
{
return self.lowestYCoordinate;
}
#pragma mark - Drop Zone Methods
- (void) calcDropZonesFromCombinedVectorMappings
{
MusicTabStaffStringFretMidiMaps *staffStringFretMaps = nil;
MusicStaffDropZone *last1back = nil;
MusicStaffDropZone *dz;
[self clearDropZones];
self.currZoneHilite = nil;
// calc the uniform height of all drop zones
float zoneHeight = [self calcUniformDropZoneHeightBasedOnYGap];
// update the members consulted for swipe up and boundary dimensions
[self establishLeftAndRightXBoundaryFromMetrics];
// accessing the _staffVectorMappings thru the combined indexing array
// delivers staff positions in sequence starting at the lowest Space of staff
NSInteger dropZoneId = 1;
for (NSNumber *ixOff in self.loToHiCombinedMappingsIndex) {
staffStringFretMaps = [self.staffVectorMappings objectAtIndex:ixOff.integerValue];
if (staffStringFretMaps) {
// calc a bounding rect based on 1/2 zoneHeight either side of the
// 'Y' postion of the staffMapping for given string (e.g. line on staff)
SWFVector3 *mappingVector = staffStringFretMaps.vec;
float zoneTop = mappingVector.y - (zoneHeight / 2.);
float zoneBottom = mappingVector.y + (zoneHeight / 2.);
SWFBoundingBox boundingBox;
boundingBox.llr_x = self.leftmostX;
boundingBox.llr_y = zoneBottom;
boundingBox.llr_z = 0;
boundingBox.upr_x = self.rightmostX;
boundingBox.upr_y = zoneTop;
boundingBox.upr_z = 0;
// Create a guitar staff oriented Drop zone
dz = [[MusicStaffDropZone alloc] initWithBoundingBox:&boundingBox
andType:staffStringFretMaps.zoneType
guitarString:staffStringFretMaps.stringNumber
andFretMaps:staffStringFretMaps.allFretMaps];
dz.staffId = self.staffLayoutInfo.staffId;
dz.noteId = kMusicNoteIdMax;
dz.octave = kOctaveNone;
// do the linked list thing
if (ixOff.integerValue == 0) {
last1back = dz;
} else {
dz.previousZone = last1back;
last1back.nextZone = dz;
last1back = dz;
}
dz.zoneId = dropZoneId;
[self addDropZone:dz];
dropZoneId++;
}
}
}
- (BOOL) doDropZoneSetsMatch:(NSArray *)zoneSet1 secondSet:(NSArray *)zoneSet2
{
BOOL setsEqual = NO;
if (zoneSet1.count != zoneSet2.count) {
return NO;
}
MusicStaffDropZone *zoneA;
MusicStaffDropZone *zoneB;
setsEqual = YES;
for (int i = 0; i < zoneSet1.count; i++) {
zoneA = [zoneSet1 objectAtIndex:i];
zoneB = [zoneSet2 objectAtIndex:i];
if (!kVALID_GUITAR_STRING(zoneA.guitarString)) {
setsEqual = NO;
continue;
}
if (zoneA.guitarString == zoneB.guitarString) {
setsEqual = YES;
} else {
setsEqual = NO;
break;
}
}
return setsEqual;
}
- (float) distanceBetweenStaffLines
{
return self.staffMetrics.yGapBetweenLines;
}
- (void) remapStaffCoordinates
{
// NSLog(@"Derived MusicGuitarTabSixStaff remapStaffCoords");
}
#pragma mark - MusicStaffTraversable_p Protocol methods
- (NSArray *) traversableLines
{
return self.staffVectorMappings;
}
- (NSArray *) traversableSpaces
{
return nil;
}
#pragma mark - Calc Rectangles
- (void) calcClefDrawingRectsWithLeftMostX:(CGFloat)leftMostX topMostY:(CGFloat)topMostY
areaSize:(CGSize)areaSize andFont:(UIFont *)theFont
intoRenderable:(MusicTabClefRenderable *) tabClefRenderable
{
CGRect rectT = CGRectZero, rectA = CGRectZero, rectB = CGRectZero;
// Divide the given area height into 3 'slots', where the rects are
// initially placed.
// Each rect though will be given the height of the font ascender
// (even if this exceeds the slot Height), to ensure the full character is rendered.
// Finally the 'Y' origin is yanked 'up' by the distance of the font's descender,
// this ensures the rendered chars land in the designated 'slot' positions with no
// top side gaps.
//MusicTabStaffStringFretMidiMaps *hiStringMapping = self.staffVectorMappings[self.highestLineVectorMapIndex];
//CGFloat topMostY = hiStringMapping.vec.y + self.staffMetrics.staffLineWidth;
//CGFloat leftMostX = self.staffMetrics.leftMostXOffset + self.staffMetrics.firstMeasureBarLineWidth + 1;
CGFloat eachSlotMark = (areaSize.height / 3);
eachSlotMark = [MusicStaffRenderMetrics roundToLeastHalfPoint:eachSlotMark];
topMostY = [MusicStaffRenderMetrics roundToLeastHalfPoint:topMostY];
leftMostX = [MusicStaffRenderMetrics roundToLeastHalfPoint:leftMostX];
areaSize.width = [MusicStaffRenderMetrics roundToLeastHalfPoint:areaSize.width];
// left side origin and widths are never altered
rectT.origin.x = rectA.origin.x = rectB.origin.x = leftMostX;
rectT.size.width = rectA.size.width = rectB.size.width = areaSize.width;
// lay these down vertically along the pre calc'd slot positions
rectT.origin.y = topMostY;
topMostY += eachSlotMark;
rectA.origin.y = topMostY;
topMostY += eachSlotMark ;
rectB.origin.y = topMostY;
// ensure each rect is tall enough for Capital Letters (only)
CGFloat fontAscender = ceilf(theFont.ascender);
rectT.size.height = rectA.size.height = rectB.size.height = fontAscender;
// sleight of hand here, pull the rects 'up' by descender value, to
// snug-fit them to desired locations be aware the descender is
// often (always ?) a negative value
rectT.origin.y -= fabsf(theFont.descender);
rectA.origin.y -= fabsf(theFont.descender);
rectB.origin.y -= fabsf(theFont.descender);
rectT.origin.y = [MusicStaffRenderMetrics roundToLeastHalfPoint:rectT.origin.y];
rectA.origin.y = [MusicStaffRenderMetrics roundToLeastHalfPoint:rectA.origin.y];
rectB.origin.y = [MusicStaffRenderMetrics roundToLeastHalfPoint:rectB.origin.y];
// place results in passed parameter
[tabClefRenderable clearRects];
tabClefRenderable.rectForT = rectT;
tabClefRenderable.rectForA = rectA;
tabClefRenderable.rectForB = rectB;
}
- (CGRect) boundingRectForClef
{
NSDictionary *attrsDict = [super textAttributesDictionaryForClef];
if (attrsDict == nil) {
return CGRectZero;
}
CGRect boundingClefRect = CGRectZero;
CGSize clefArea = {0};
MusicTabStaffStringFretMidiMaps *lowStringMapping = self.staffVectorMappings[self.lowestLineVectorMapIndex];
MusicTabStaffStringFretMidiMaps *hiStringMapping = self.staffVectorMappings[self.highestLineVectorMapIndex];
clefArea.height = lowStringMapping.vec.y - hiStringMapping.vec.y;
CGSize strSize = [_tabClefRenderable.letterA sizeWithAttributes:attrsDict];
clefArea.width = strSize.width;
// calc bounding rects for each of the veritally aligned letters
CGFloat topMostY = hiStringMapping.vec.y + self.staffMetrics.staffLineWidth;
CGFloat leftMostX = self.staffMetrics.leftMostXOffset + self.staffMetrics.firstMeasureBarLineWidth + 1;
[self calcClefDrawingRectsWithLeftMostX:leftMostX topMostY:topMostY
areaSize:clefArea andFont:attrsDict[NSFontAttributeName]
intoRenderable:_tabClefRenderable];
CGPoint clefOrigin = _tabClefRenderable.rectForT.origin;
CGSize clefSize = _tabClefRenderable.rectForA.size; // assuming A is widest character
CGFloat bottomOfB = _tabClefRenderable.rectForB.origin.y + _tabClefRenderable.rectForB.size.height;
clefSize.height = bottomOfB - clefOrigin.y;
boundingClefRect.origin = clefOrigin;
boundingClefRect.size = clefSize;
return boundingClefRect;
}
#pragma mark - Draw Rendering Methods
- (void) drawClefOnStaffInContext:(CGContextRef)ctx
{
NSDictionary *attrsDict = [super textAttributesDictionaryForClef];
if (attrsDict == nil) {
return;
}
// calling this method forces the internal calcs, for our drawing though we don't need the Rect
[self boundingRectForClef];
UIGraphicsPushContext(ctx);
CGContextSaveGState(ctx);
CGContextSetAllowsAntialiasing(ctx, YES);
CGContextSetShouldAntialias(ctx, YES);
CGContextSetShouldSmoothFonts(ctx, YES);
[_tabClefRenderable.letterT drawInRect:_tabClefRenderable.rectForT
withAttributes:attrsDict];
[_tabClefRenderable.letterA drawInRect:_tabClefRenderable.rectForA
withAttributes:attrsDict];
[_tabClefRenderable.letterB drawInRect:_tabClefRenderable.rectForB
withAttributes:attrsDict];
CGContextRestoreGState(ctx);
UIGraphicsPopContext();
}
- (void) drawStaffInContext:(CGContextRef)ctx withMeasureCount:(int)measureCount
{
CGFloat leftX = self.staffMetrics.leftMostXOffset;
CGFloat rightX = leftX + (measureCount * self.staffMetrics.lineLengthOneMeasure);
CGFloat lineGap = self.staffMetrics.yGapBetweenLines;
// calc top lines Y offset, and round to land on half pixel
CGFloat firstY = self.staffYCenter - (self.staffMetrics.staffHeight / 2);
if (self.staffMetrics.staffLineWidth == 1) {
firstY = [MusicStaffRenderMetrics roundToLeastHalfPoint:firstY];
} else if (self.staffMetrics.staffLineWidth == 2) {
firstY = floorf(firstY);
}
// these allow vertical bar lines to 'extend' part way into intersection horz line
CGFloat barLineTopY = firstY - (self.staffMetrics.staffLineWidth / 2);
CGFloat barLineBottomY = firstY + (self.staffMetrics.staffLineWidth / 2);
UIGraphicsPushContext(ctx);
CGContextSaveGState(ctx);
CGContextSetStrokeColorWithColor(ctx, self.staffMetrics.lineColor.CGColor);
CGContextSetLineWidth(ctx, self.staffMetrics.staffLineWidth);
int numFixedLines = self.staffMetrics.fixedLineCount;
for (int x=0; x < numFixedLines; x++) {
CGContextMoveToPoint(ctx, leftX, firstY);
CGContextAddLineToPoint(ctx, rightX, firstY);
CGContextStrokePath(ctx);
firstY += lineGap;
if (x < (numFixedLines - 1)) {
barLineBottomY += lineGap;
}
}
CGContextRestoreGState(ctx);
UIGraphicsPopContext();
// now draw the vertical bar lines, optionally skipping last for open bar
[self.verticalBarCoordinates removeAllObjects];
SWFVector3 *barVec;
int barCount = measureCount + 1;
float barX = self.staffMetrics.leftMostXOffset;
float measureLength = self.staffMetrics.lineLengthOneMeasure;
UIGraphicsPushContext(ctx);
CGContextSaveGState(ctx);
CGContextSetStrokeColorWithColor(ctx, self.staffMetrics.lineColor.CGColor);
for (int z = 0; z < barCount; z++) {
if (z == 0) {
CGContextSetLineWidth(ctx, self.staffMetrics.firstMeasureBarLineWidth);
} else {
CGContextSetLineWidth(ctx, self.staffMetrics.measureBarLineWidth);
}
// always draw vertical bars, ...but sometimes don't draw the last bar (guitar TAB)
if (((self.staffMetrics.lastMeasureOpenBar == YES) && (z + 1 < barCount)) ||
(self.staffMetrics.lastMeasureOpenBar == NO)) {
CGContextMoveToPoint(ctx, barX, barLineTopY);
CGContextAddLineToPoint(ctx, barX, barLineBottomY);
CGContextStrokePath(ctx);
}
// stash their locations for further use
barVec = [SWFVector3 vectorFromValues:barX y:barLineTopY z:barLineBottomY];
[self.verticalBarCoordinates addObject:barVec];
barX += measureLength;
}
CGContextRestoreGState(ctx);
UIGraphicsPopContext();
}
- (void) drawStubLineInContext:(CGContextRef)ctx
withNotePosition:(SWFVector3 *)noteVec
andNoteGeometry:(SWFVector3 *)noteGeometry
forNoteId:(kMusicNoteId)noteId
inOctave:(kMusicStaffOctave)octave
{
// override to do nothing TAB has no stubLines
}
@end
//
// MusicMidiNoteFrequenciesPianoTable.h
// MusicTheory
//
// Created by Michael J Albanese on 4/16/14.
// Copyright (c) 2014 Michael J Albanese. All rights reserved.
//
#import <Foundation/Foundation.h>
#import "MusicTheoryDefs.h"
#define kMusicMidiCode_MiddleC 60
#define kMusicMidiCode_PitchA440 69
@class MusicMidiNoteFrequencyEntry;
@interface MusicMidiNoteFrequenciesPianoTable : NSObject
@property (nonatomic, readonly) BOOL tableBuilt;
+ (instancetype) midiNoteFrequenciesPianoTable;
- (void) buildFrequenciesTable;
- (MusicMidiNoteFrequencyEntry *) findEntryByMidiCode:(NSInteger)midiCode;
- (MusicMidiNoteFrequencyEntry *) findMiddleCEntry;
- (MusicMidiNoteFrequencyEntry *) findPitchA440Entry;
- (MusicMidiNoteFrequencyEntry *) findEntryByNoteId:(kMusicNoteId)noteId
andMidiOctave:(NSInteger)octave;
// May need an auxiliary Octave --> frequency range mapping
@end
//
// MusicMidiNoteFrequenciesPianoTable.m
// MusicTheory
//
// Created by Michael J Albanese on 4/16/14.
// Copyright (c) 2014 Michael J Albanese. All rights reserved.
//
#import "MusicMidiNoteFrequencyEntry.h"
#import "MusicMidiNoteFrequenciesPianoTable.h"
NSString *kMidiCodeFrequenciesFileName = @"MidiCodeFrequencies";
@interface MusicMidiNoteFrequenciesPianoTable ()
@property (nonatomic, readwrite) BOOL tableBuilt;
@property (strong, nonatomic) NSMutableDictionary *frequenciesTable;
@property (strong, nonatomic) NSMutableArray *sortedEntries;
@end
@implementation MusicMidiNoteFrequenciesPianoTable
+ (instancetype) midiNoteFrequenciesPianoTable
{
return [[MusicMidiNoteFrequenciesPianoTable alloc] initFrequenciesTable];
}
- (id) initFrequenciesTable
{
if (self = [super init]) {
_frequenciesTable = [NSMutableDictionary dictionary];
_sortedEntries = [NSMutableArray array];
}
return self;
}
- (void) dealloc
{
[_frequenciesTable removeAllObjects];
}
- (MusicMidiNoteFrequencyEntry *) findEntryByMidiCode:(NSInteger)midiCode
{
if (_frequenciesTable == nil || _frequenciesTable.count < midiCode) {
return nil;
}
MusicMidiNoteFrequencyEntry *entry = _frequenciesTable[ @(midiCode) ];
return entry;
}
- (MusicMidiNoteFrequencyEntry *) findEntryByNoteId:(kMusicNoteId)noteId
andMidiOctave:(NSInteger)octave
{
MusicMidiNoteFrequencyEntry *foundEntry;
// Brute force linear scane for now, still only 88 entries ...small
for (MusicMidiNoteFrequencyEntry *entry in _sortedEntries) {
if (entry.noteId == noteId && entry.octave == octave) {
foundEntry = entry;
break;
}
}
return foundEntry;
}
- (MusicMidiNoteFrequencyEntry *) findMiddleCEntry
{
if (_frequenciesTable == nil || _frequenciesTable.count < kMusicMidiCode_MiddleC) {
return nil;
}
MusicMidiNoteFrequencyEntry *entry = _frequenciesTable[ @(kMusicMidiCode_MiddleC) ];
return entry;
}
- (MusicMidiNoteFrequencyEntry *) findPitchA440Entry
{
if (_frequenciesTable == nil || _frequenciesTable.count < kMusicMidiCode_PitchA440) {
return nil;
}
MusicMidiNoteFrequencyEntry *entry = _frequenciesTable[ @(kMusicMidiCode_PitchA440) ];
return entry;
}
#pragma mark - Internal Build Methods
- (void) buildFrequenciesTable
{
NSURL *urlForMidiCodeFile = [self loadMidiCodeSourceListingFile];
if (urlForMidiCodeFile != nil) {
[self loadKeysAndParseDictionaryFromUrl:urlForMidiCodeFile];
}
}
- (NSURL *) loadMidiCodeSourceListingFile
{
// This will always run in the context of an App, and therefore is dependent
// upon the application to have copied the MusicTheoryBundle target into the
// Copy Bundle Resources section of the applications Build Phases -> Copy Bundle Resources
// Load the library's bundle object from Apps resources
NSString *libraryBundlePath = [[NSBundle mainBundle] pathForResource:@"MusicTheoryBundle"
ofType:@"bundle"];
NSBundle *staticLibraryBundle = [NSBundle bundleWithPath:libraryBundlePath];
NSAssert(staticLibraryBundle != nil, @"MusicTheoryBundle NOT loaded");
NSURL *midiUrl = [staticLibraryBundle URLForResource:kMidiCodeFrequenciesFileName
withExtension:@"plist"];
return midiUrl;
}
- (BOOL) loadKeysAndParseDictionaryFromUrl:(NSURL *)midiCodeListingUrl
{
MusicMidiNoteFrequencyEntry *entry, *lastEntry;
NSInteger totalCodeCount = 88;
// Load the file content and read the data into arrays
NSDictionary *tempDict = [[NSDictionary alloc] initWithContentsOfURL:midiCodeListingUrl];
if (tempDict == nil || tempDict.count != 4) {
return NO;
}
// Grab the known / named 4 sub arrays
NSArray *arMidiCodes = [tempDict objectForKey:@"MidiCode"];
NSArray *arNoteName = [tempDict objectForKey:@"NoteName"];
NSArray *arFrequencies = [tempDict objectForKey:@"Frequency"];
NSArray *arPeriods = [tempDict objectForKey:@"Period"];
if (arMidiCodes.count != totalCodeCount || arNoteName.count != totalCodeCount ||
arFrequencies.count != totalCodeCount || arPeriods.count != totalCodeCount) {
return NO;
}
[_frequenciesTable removeAllObjects];
_tableBuilt = NO;
NSString *singleLetter, *octaveLetter;
NSNumber *previousMidiCode;
// Loop over the set of 4 arrays and build frequency Dictionary
for (NSInteger offset = 0; offset < totalCodeCount; offset++) {
NSNumber *midiNumberObj = arMidiCodes[offset];
NSString *noteName = arNoteName[offset];
NSNumber *frequencyNumObj = arFrequencies[offset];
NSNumber *periodNumObj = arPeriods[offset];
entry = [MusicMidiNoteFrequencyEntry midiNoteFrequencyEntry:midiNumberObj.integerValue];
entry.frequency = frequencyNumObj.floatValue;
entry.period = periodNumObj.floatValue;
if ([noteName isEqualToString:@"Black"]) {
entry.isBlackKey = YES;
entry.noteId = kMusicNoteIdEnharmonic;
entry.octave = lastEntry.octave;
entry.previousKeyMidiCode = previousMidiCode.integerValue;
} else {
singleLetter = [noteName substringWithRange:NSMakeRange(0, 1)];
octaveLetter = [noteName substringWithRange:NSMakeRange(1, 1)];
entry.noteId = [self noteIdFromLetterString:singleLetter];
entry.octave = [octaveLetter integerValue];
entry.codedNoteName = noteName;
previousMidiCode = arMidiCodes[offset];
}
_frequenciesTable[ @(midiNumberObj.integerValue) ] = entry;
lastEntry = entry;
}
if (_frequenciesTable.count == totalCodeCount) {
[self sortDictionaryValues];
_tableBuilt = YES;
}
return _tableBuilt;
}
- (void) sortDictionaryValues
{
NSArray *allEntries = [_frequenciesTable allValues];
NSSortDescriptor *sd;
NSString *kvSortPath = @"midiCode";
sd = [NSSortDescriptor sortDescriptorWithKey:kvSortPath ascending:YES];
NSArray *arSorted = [allEntries sortedArrayUsingDescriptors:@[sd]];
[_sortedEntries addObjectsFromArray:arSorted];
}
- (kMusicNoteId) noteIdFromLetterString:(NSString *)singleLetter
{
kMusicNoteId aNoteId = kMusicNoteIdMax;
if ([singleLetter isEqualToString:@"C"]) {
aNoteId = kMusicNoteId_C;
} else if ([singleLetter isEqualToString:@"D"]) {
aNoteId = kMusicNoteId_D;
} else if ([singleLetter isEqualToString:@"E"]) {
aNoteId = kMusicNoteId_E;
} else if ([singleLetter isEqualToString:@"F"]) {
aNoteId = kMusicNoteId_F;
} else if ([singleLetter isEqualToString:@"G"]) {
aNoteId = kMusicNoteId_G;
} else if ([singleLetter isEqualToString:@"A"]) {
aNoteId = kMusicNoteId_A;
} else if ([singleLetter isEqualToString:@"B"]) {
aNoteId = kMusicNoteId_B;
}
return aNoteId;
}
@end
//
// MusicMidiNoteFrequencyEntry.h
// MusicTheory
//
// Created by Michael J Albanese on 4/16/14.
// Copyright (c) 2014 Michael J Albanese. All rights reserved.
//
#import <Foundation/Foundation.h>
#import "MusicTheoryDefs.h"
/**
* Small class represents basic entry in the MidiNoteFrequenciesTable
*/
@interface MusicMidiNoteFrequencyEntry : NSObject
@property (nonatomic) NSInteger midiCode;
@property (nonatomic) kMusicNoteId noteId;
@property (nonatomic) NSInteger octave;
@property (nonatomic) float frequency;
@property (nonatomic) float period;
@property (nonatomic) BOOL isBlackKey;
@property (nonatomic) NSInteger previousKeyMidiCode;
@property (nonatomic, copy) NSString *codedNoteName;
// When the kMusicStaffOctave enums get reworked, start using this
// @property (nonatomic) kMusicStaffOctave octave;
+ (instancetype) midiNoteFrequencyEntry:(NSInteger)code;
@end
//
// MusicMidiNoteFrequencyEntry.m
// MusicTheory
//
// Created by Michael J Albanese on 4/16/14.
// Copyright (c) 2014 Michael J Albanese. All rights reserved.
//
#import "MusicMidiNoteFrequencyEntry.h"
@implementation MusicMidiNoteFrequencyEntry
+ (instancetype) midiNoteFrequencyEntry:(NSInteger)code
{
return [[MusicMidiNoteFrequencyEntry alloc] initWithMidiCode:code];
}
- (id) initWithMidiCode:(NSInteger)code
{
if (self = [super init]) {
_midiCode = code;
_noteId = kMusicNoteIdMax;
}
return self;
}
@end
//
// MusicMidiNoteOctave.h
// MusicTheory
//
// Created by Michael J Albanese on 4/25/14.
// Copyright (c) 2014 Michael J Albanese. All rights reserved.
//
#import <Foundation/Foundation.h>
#import "MusicTheoryDefs.h"
@interface MusicMidiNoteOctave : NSObject
@property (nonatomic) kMusicNoteId noteId;
@property (nonatomic) NSInteger octave;
@property (nonatomic) NSInteger midiCode;
@property (nonatomic, readonly) BOOL isValidMidiCode;
@property (nonatomic, readonly) BOOL isValidOctave;
@property (nonatomic, readonly) BOOL isValidNote;
+ (instancetype) noteOctaveWithNote:(kMusicNoteId)noteId
octave:(NSInteger)octave midiCode:(NSInteger)midiCode;
+ (instancetype) noteOctaveWithNote:(kMusicNoteId)noteId
andOctave:(NSInteger)octave;
+ (NSInteger) mapMusicStaffOctaveToMidiOctave:(kMusicStaffOctave)staffOctave;
+ (NSInteger) adjustOctaveForAltoStaff:(NSInteger)anOctave;
@end
//
// MusicMidiNoteOctave.m
// MusicTheory
//
// Created by Michael J Albanese on 4/25/14.
// Copyright (c) 2014 Michael J Albanese. All rights reserved.
//
#import "MusicMidiNoteOctave.h"
@implementation MusicMidiNoteOctave
+ (instancetype) noteOctaveWithNote:(kMusicNoteId)noteId
octave:(NSInteger)octave
midiCode:(NSInteger)midiCode
{
return [[MusicMidiNoteOctave alloc] initWithNote:noteId
andOctave:octave midiCode:midiCode];
}
+ (instancetype) noteOctaveWithNote:(kMusicNoteId)noteId
andOctave:(NSInteger)octave
{
return [[MusicMidiNoteOctave alloc] initWithNote:noteId
andOctave:octave midiCode:0];
}
+ (NSInteger) mapMusicStaffOctaveToMidiOctave:(kMusicStaffOctave)staffOctave
{
NSInteger actualMidiOctave = 1;
if (staffOctave == kOctaveMinusTwo) {
actualMidiOctave = 2;
} else if (staffOctave == kOctaveMinusOne) {
actualMidiOctave = 3;
} else if (staffOctave == kOctaveHome) {
actualMidiOctave = 4;
} else if (staffOctave == kOctavePlusOne) {
actualMidiOctave = 5;
} else if (staffOctave == kOctavePlusTwo) {
actualMidiOctave = 6;
} else if (staffOctave == kOctavePlusThree) {
actualMidiOctave = 7;
}
// these are TEMPorary until all kMusicStaffOctave enums are matched up with Midi 0 - 8
return actualMidiOctave;
}
+ (NSInteger) adjustOctaveForAltoStaff:(NSInteger)anOctave
{
NSInteger altoOctave = anOctave;
if (anOctave > 1) {
altoOctave = anOctave - 2;
}
return altoOctave;
}
- (id) initWithNote:(kMusicNoteId)noteId andOctave:(NSInteger)octave midiCode:(NSInteger)midiCode
{
if (self = [super init]) {
_noteId = noteId;
_octave = octave;
if (midiCode >= 21 && midiCode <= 108) {
_midiCode = midiCode;
}
}
return self;
}
- (BOOL) isValidMidiCode
{
if (_midiCode >= 21 && _midiCode <= 108) {
return YES;
}
return NO;
}
- (BOOL) isValidOctave
{
if (_octave >= 0 && _octave <= 8) {
return YES;
}
return NO;
}
- (BOOL) isValidNote
{
return kVALID_NOTE_ID(_noteId);
}
@end
//
// MusicStaff.h
// SongData1
//
// Created by Michael J Albanese on 10/9/12.
// Copyright (c) 2013 WayFwonts.com. All rights reserved.
//
#import <UIKit/UIKit.h>
#import <Foundation/Foundation.h>
#import "MusicTheoryDefs.h"
#import "SWFDroppable_p.h"
#import "MusicStaffDisplayable_p.h"
@class SWFVector3;
@class NoteData;
@class MusicStaffRenderMetrics;
@class MusicPageRenderCriteria;
@class MusicStaffLineVectorMap;
@protocol MusicStaffNotifications_p;
/**
* Renderable displayable MusicStaff object. This class is a Drag n Drop target by
* virture of adopting the SWFDroppable_p protocol. To accomodate such a protocol
* this class defines and maintaines a set of internal 'Drop Zones', valid locations
* which offer valid coordinates for Draggable entities to be dropped.
*
* Additionally this class is displayable and can render a MusicStaff in a given context.
* Other than the veneer of a music staff, this class does _not_ maintain any residents.
* Besides an internal mapping of note id's and octaves that serve as information given
* within a Drag n Drop exchange, this class is agnostic to any music entities. It is the
* MusicMeasure (and its set of MusicMeasureColumns) which worry about placement and
* housing of music entity objects.
*
* This MusicStaff is like a boiler plate staff that can render, given a set of
* MusicScoreStaffMetrics, and create an internal plotting given a MusicStaffLayoutInfo
* structure.
*
* @seealso MusicScoreStaffMetrics
* @seealso MusicStaffLayoutInfo
* @seealso MusicPageRenderCriteria
* @seealso MusicStaffDisplayable_p
* @seealso SWFDroppable_p
*/
@interface MusicStaff : NSObject <MusicStaffDisplayable_p, SWFDroppable_p>
@property (weak, nonatomic) id<MusicStaffNotifications_p> staffDelegate;
@property (weak, nonatomic, readonly, getter = findLowestLineVectorMap) MusicStaffLineVectorMap *lowestLineVectorMap;
@property (weak, nonatomic, readonly, getter = findHighestLineVectorMap) MusicStaffLineVectorMap *highestLineVectorMap;
@property (nonatomic, readonly) MusicStaffLayoutInfo staffLayoutInfo;
@property (nonatomic, readonly, getter = myStaffName) NSString *name;
@property (nonatomic, getter = retrieveSwipeUpBounds, readonly) CGRect boundsForSwipeUp;
@property (nonatomic) NSInteger lowestLineVectorMapIndex;
@property (nonatomic) NSInteger highestLineVectorMapIndex;
@property (nonatomic, readonly) CGFloat topLineY;
@property (nonatomic, readonly) CGFloat bottomLineY;
@property (strong, nonatomic, readonly) NSMutableArray *verticalBarCoordinates;
/** Array of all spaces/lines in staff and their mapped notes, octaves and 'type' */
@property (strong, nonatomic) NSMutableArray *staffVectorMappings;
@property (strong, nonatomic) NSMutableArray *linesVectorMappingsIndex;
@property (strong, nonatomic) NSMutableArray *spacesVectorMappingsIndex;
@property (strong, nonatomic) NSMutableArray *loToHiCombinedMappingsIndex;
/** calc'd during DropZone calculations and used for swipe gesture bounds */
@property (nonatomic) float leftmostX;
@property (nonatomic) float rightmostX;
@property (nonatomic) float highestYCoordinate;
/** measurements and properties describing the visual appearance of Music Staff */
@property (strong, nonatomic) MusicPageRenderCriteria *pageRenderCriteria;
@property (strong, nonatomic) SWFVector3 *lowestLineVector;
@property (strong, nonatomic) SWFVector3 *lowestSpaceVector;
@property (strong, nonatomic) SWFVector3 *middleCVector;
/** Following are needed by derived class, Obj C forces us to place in public header !*&% */
/** flag/object reference to currently hilighted (single) dropZone */
@property (strong, nonatomic) MusicStaffDropZone *currZoneHilite;
/** Array of calculated DropZone objects used in Drag Drop hit testing */
// @property (strong, nonatomic) NSMutableArray *dropZones;
+ (MusicStaff *) musicStaffWithLayoutInfo:(MusicStaffLayoutInfo *)layoutInfo
andMiddleXCoordinate:(float)middleX
andLowestYCoordinate:(float)lowY
andStaffMetrics:(MusicStaffRenderMetrics *)staffMetrics
andPageRenderCriteria:(MusicPageRenderCriteria *)pageCriteria;
- (id) initWithLayoutInfo:(MusicStaffLayoutInfo *)layoutInfo
andMiddleXCoordinate:(float)middleX
andLowestYCoordinate:(float)lowY
andStaffMetrics:(MusicStaffRenderMetrics *)staffMetrics
andPageRenderCriteria:(MusicPageRenderCriteria *)pageCriteria;
- (void) drawStaffInContext:(CGContextRef)ctx
withMeasureCount:(int)measureCount;
- (void) drawStubLineInContext:(CGContextRef)ctx
withNotePosition:(SWFVector3 *)noteVec
forNoteId:(kMusicNoteId)noteId
inOctave:(kMusicStaffOctave)octave;
- (void) drawClefOnStaffInContext:(CGContextRef)ctx;
- (SWFVector3 *) findStaffVectorForNoteUsingItsOctave:(NoteData *)aNote;
- (CGPoint) findStaffCoordinateForNoteUsingItsOctave:(NoteData *)aNote;
- (CGPoint) findStaffCoordinateForNoteId:(kMusicNoteId)noteId
inOctave:(kMusicStaffOctave)oct;
- (kMusicStaffZone) findStaffZoneTypeForNoteId:(kMusicNoteId)noteId
inOctave:(kMusicStaffOctave)oct;
- (MusicStaffLineVectorMap *) findStaffVectorMapForNoteId:(kMusicNoteId)noteId
inOctave:(kMusicStaffOctave)oct;
- (float) stubLineYCoordinateForNoteId:(kMusicNoteId)noteId
inOctave:(kMusicStaffOctave)octave
withNotePosition:(SWFVector3 *)noteVec;
/**
* @return combined array of both line and space vector mappings
*/
- (NSArray *) staffVectorMappingsBottomUp;
/** mainly used by derived classes */
- (void) clearDropZones;
/** called as part of building the Drop zones in any staff */
- (void) establishLeftAndRightXBoundaryFromMetrics;
/** another utility called from Drop zone calculations in any staff */
- (float) calcUniformDropZoneHeightBasedOnYGap;
/** Allows derived staffs to examine their specific drop zone criteria */
- (BOOL) doDropZoneSetsMatch:(NSArray *)zoneSet1 secondSet:(NSArray *)zoneSet2;
/** TEMP Until FontAndNamesManager class created */
- (NSDictionary *) textAttributesDictionaryForClef;
- (CGRect) boundingRectForClefString:(NSString *)clefString
usingAttrs:(NSDictionary *)attrsDict atOrigin:(CGPoint)ptOrigin;
@end
//
// MusicStaff.m
//
// Created by Michael J Albanese on 10/9/12.
// Copyright (c) 2014 WayFwonts.com. All rights reserved.
//
#import <CoreText/CoreText.h>
#import "MusicStaffRenderMetrics.h"
#import "NoteData+MoreFuncs.h"
#import "SWFDraggable.h"
#import "MusicStaffDropReply.h"
#import "MusicStaffDropRequest.h"
#import "MusicStaffDropZone.h"
#import "MusicStaffNotifications_p.h"
#import "MusicPageRenderCriteria.h"
#import "MusicStaffLineVectorMap.h"
#import "MusicNamesFormatter.h"
#import "MusicStaff.h"
#pragma mark - MusicStaff main Class begins
@interface MusicStaff ()
@property (strong, nonatomic) NSMutableArray *dropZones;
@property (strong, nonatomic) NSMutableArray *multiZoneHilite;
@property (strong, nonatomic, readwrite) NSMutableArray *verticalBarCoordinates;
@property (nonatomic, readwrite) CGFloat topLineY;
@property (nonatomic, readwrite) CGFloat bottomLineY;
/** attempt at quasi protected methods for subclasses to override */
- (void) mapStaffOctaveAndNoteCoordinates;
- (void) calcDropZonesFromCombinedVectorMappings;
@end
@implementation MusicStaff
/** Need to manually synthesize properties delared in the Protocol */
@synthesize staffId = _staffId;
@synthesize clefId = _clefId;
@synthesize staffMetrics = _staffMetrics;
@synthesize isVisible = _isVisible;
@synthesize isSelected = _isSelected;
@synthesize lowestYCoordinate = _lowestYCoordinate;
@synthesize centerYCoordinate = _centerYCoordinate;
@synthesize staffYCenter = _staffYCenter;
@synthesize middleXCoordinate = _middleXCoordinate;
+ (MusicStaff *) musicStaffWithLayoutInfo:(MusicStaffLayoutInfo *)layoutInfo
andMiddleXCoordinate:(float)middleX
andLowestYCoordinate:(float)lowY
andStaffMetrics:(MusicStaffRenderMetrics *)staffMetrics
andPageRenderCriteria:(MusicPageRenderCriteria *)renderCriteria
{
return [[self alloc] initWithLayoutInfo:layoutInfo
andMiddleXCoordinate:middleX
andLowestYCoordinate:lowY
andStaffMetrics:staffMetrics
andPageRenderCriteria:renderCriteria];
}
- (id) initWithLayoutInfo:(MusicStaffLayoutInfo *)layoutInfo
andMiddleXCoordinate:(float)middleX
andLowestYCoordinate:(float)lowY
andStaffMetrics:(MusicStaffRenderMetrics *)staffMetrics
andPageRenderCriteria:(MusicPageRenderCriteria *)renderCriteria
{
if (self = [super init]) {
_isVisible = YES;
_staffLayoutInfo = *layoutInfo;
_middleXCoordinate = middleX;
_lowestYCoordinate = lowY;
_staffMetrics = staffMetrics;
_pageRenderCriteria = renderCriteria;
_lowestLineVectorMapIndex = _highestLineVectorMapIndex = kMaxStaffLines + 10;
_dropZones = [NSMutableArray array];
_staffVectorMappings = [NSMutableArray array];
_verticalBarCoordinates = [NSMutableArray array];
_linesVectorMappingsIndex = [NSMutableArray array];
_spacesVectorMappingsIndex = [NSMutableArray array];
_loToHiCombinedMappingsIndex = [NSMutableArray array];
[self mapStaffOctaveAndNoteCoordinates];
[self calcDropZonesFromCombinedVectorMappings];
}
return self;
}
- (void) dealloc
{
[_staffVectorMappings removeAllObjects];
_staffVectorMappings = nil;
[_dropZones removeAllObjects];
_dropZones = nil;
[_multiZoneHilite removeAllObjects];
_multiZoneHilite = nil;
[_staffVectorMappings removeAllObjects];
[_verticalBarCoordinates removeAllObjects];
[_linesVectorMappingsIndex removeAllObjects];
[_spacesVectorMappingsIndex removeAllObjects];
[_loToHiCombinedMappingsIndex removeAllObjects];
}
- (kSongStaffId) staffId
{
return _staffLayoutInfo.staffId;
}
- (kSongClefId) clefId
{
return _staffLayoutInfo.clefId;
}
- (NSString *) myStaffName
{
NSString *myName = nil;
if (_staffLayoutInfo.staffId == kStaffTreble) {
myName = @"Treble Staff";
} else if (_staffLayoutInfo.staffId == kStaffBass) {
myName = @"Bass Staff";
} else if (_staffLayoutInfo.staffId == kStaffTABSix) {
myName = @"Guitar TAB";
} else if (_staffLayoutInfo.staffId == kStaffTABFour) {
myName = @"Bass Guitar TAB";
} else if (_staffLayoutInfo.staffId == kStaffMelody) {
myName = @"Melody Staff";
}
return myName;
}
#pragma mark - Custom Bounds Getters
- (CGRect) retrieveCalcdBounds
{
// Note: this is not a traditional CGRect as last element is not the height
// but the bottom Y coordinate, e.g. these are 2 points TL and BR
return CGRectMake(_leftmostX, _highestYCoordinate, _rightmostX, _lowestYCoordinate);
}
- (CGRect) retrieveSwipeUpBounds
{
// for chords residing at/near bottom of staff, the starting point of
// a swipe up often begins in the staff below this staff's actual bounds.
// In order for swipeUp detection to appear accurate to the user, and deal
// with the fact that iOS reports only the beginning touch of a swipe, we
// temporarily extend our bottom bounds.
return CGRectMake(_leftmostX,
_highestYCoordinate,
_rightmostX,
_lowestYCoordinate + (2. * _staffMetrics.yGapBetweenLines));
}
#pragma mark - Mapping Staff Verticies
- (void) remapStaffCoordinates
{
[self mapStaffOctaveAndNoteCoordinates];
[self calcDropZonesFromCombinedVectorMappings];
}
- (void) mapStaffOctaveAndNoteCoordinates
{
// All mapping is constructed from the bottom of the staff 'Up' to top
float x = _middleXCoordinate;
float y = _lowestYCoordinate;
float z = 0.;
int mapOffset = 0;
_lowestLineVectorMapIndex = _highestLineVectorMapIndex = kMaxStaffLines + 10;
_highestYCoordinate = 2000.0; // because in 2D 0,0 is top left
float yincr = _staffMetrics.yGapBetweenLines;
if (!_staffVectorMappings) {
_staffVectorMappings = [NSMutableArray array];
_linesVectorMappingsIndex = [NSMutableArray array];
_spacesVectorMappingsIndex = [NSMutableArray array];
_loToHiCombinedMappingsIndex = [NSMutableArray array];
} else {
[_staffVectorMappings removeAllObjects];
[_linesVectorMappingsIndex removeAllObjects];
[_spacesVectorMappingsIndex removeAllObjects];
[_loToHiCombinedMappingsIndex removeAllObjects];
}
MusicStaffLineVectorMap *staffMapping = nil;
// first loop for lines, from middle C Up
for (int i = 0; i < _staffLayoutInfo.usedStaffLines; i++, mapOffset++) {
staffMapping = [[MusicStaffLineVectorMap alloc] init];
MusicNoteToOctaveMap noteOctMap;
noteOctMap.lineSpaceIndicator = kMusicLineZone;
noteOctMap.octave = _staffLayoutInfo.lineNotes[i].octave;
noteOctMap.noteId = _staffLayoutInfo.lineNotes[i].noteId;
staffMapping.octaveInfo = noteOctMap;
staffMapping.vec = [SWFVector3 vectorFromValues:x y:y z:z];
staffMapping.zoneType = kMusicLineZone;
[_staffVectorMappings addObject:staffMapping];
if (i == _staffLayoutInfo.lineOffsetMiddleC) {
_middleCVector = staffMapping.vec;
}
if (i == 0) {
_lowestLineVector = staffMapping.vec;
_lowestLineVectorMapIndex = i;
}
if (y < _highestYCoordinate) {
_highestYCoordinate = y;
_highestLineVectorMapIndex = i;
}
// update the lines index with offset of newly added line mapping
[_linesVectorMappingsIndex addObject:@(_staffVectorMappings.count - 1)];
y -= yincr;
}
int numStaffPositions = _staffLayoutInfo.usedStaffLines + _staffLayoutInfo.usedStaffSpaces;
// next loop maps the spaces, from B below middle C Up
y = _lowestYCoordinate + (_staffMetrics.yGapBetweenLines / 2);
for (int i = 0; i < _staffLayoutInfo.usedStaffSpaces && mapOffset < numStaffPositions; i++, mapOffset++) {
staffMapping = [[MusicStaffLineVectorMap alloc] init];
MusicNoteToOctaveMap noteOctMap;
noteOctMap.lineSpaceIndicator = kMusicSpaceZone;
noteOctMap.octave = _staffLayoutInfo.spaceNotes[i].octave;
noteOctMap.noteId = _staffLayoutInfo.spaceNotes[i].noteId;
staffMapping.octaveInfo = noteOctMap;
staffMapping.vec = [SWFVector3 vectorFromValues:x y:y z:z];
staffMapping.zoneType = kMusicSpaceZone;
[_staffVectorMappings addObject:staffMapping];
if (i == 0) {
_lowestSpaceVector = staffMapping.vec;
}
if (y < _highestYCoordinate) {
_highestYCoordinate = y;
}
// update the spaces index with offset of newly added space mapping
[_spacesVectorMappingsIndex addObject:@(_staffVectorMappings.count - 1)];
y -= yincr;
}
// _highestYCoordinate -= _staffMetrics.yGapBetweenLines;
NSAssert(_highestYCoordinate > 0., @"NEGATIVE mapping of _highestYCoordinate not allowed");
// build the combined from the just composed spaces and lines separate indexes
[self buildCombinedVectorMappingsIndex];
}
#pragma mark - VectorMaps
- (void) buildCombinedVectorMappingsIndex
{
// Dependent upon both the _linesVectorMappingIndex and _spacesVectorMappingIndex
// to already be populated with accurate indexes into _staffVectorMappings array.
// This will build one array which when sequentially traverse the mappings by
// encountering the standard alternating space-line staff mappings starting with the
// lowest space mapping of this staff
NSInteger totalMappings = _staffVectorMappings.count;
if (_linesVectorMappingsIndex.count + _spacesVectorMappingsIndex.count > totalMappings) {
return;
}
[_loToHiCombinedMappingsIndex removeAllObjects];
MusicStaffLineVectorMap *staffMapping = nil;
NSInteger spaceOff = 0, lineOff = 0;
BOOL doSpace = YES;
for (NSInteger x = 0; x < totalMappings; x++) {
if (doSpace && spaceOff < _spacesVectorMappingsIndex.count) {
NSNumber *spaceIndex = [_spacesVectorMappingsIndex objectAtIndex:spaceOff++];
staffMapping = [_staffVectorMappings objectAtIndex:spaceIndex.integerValue];
if (staffMapping) {
[_loToHiCombinedMappingsIndex addObject:@(spaceIndex.integerValue)];
}
doSpace = NO;
} else if (lineOff < _linesVectorMappingsIndex.count) {
NSNumber *lineIndex = [_linesVectorMappingsIndex objectAtIndex:lineOff++];
staffMapping = [_staffVectorMappings objectAtIndex:lineIndex.integerValue];
if (staffMapping) {
[_loToHiCombinedMappingsIndex addObject:@(lineIndex.integerValue)];
}
doSpace = YES;
}
}
}
- (NSArray *) staffVectorMappingsBottomUp
{
NSMutableArray *combinedMappings = [NSMutableArray array];
MusicStaffLineVectorMap *aMap;
// While _staffVectorMappings does hold all mappings, their order
// is grouped as all lines, then all spaces. This method produces
// a interweaved resulting array as dictated by the combined indexes.
for (NSNumber *indexNum in _loToHiCombinedMappingsIndex) {
aMap = [_staffVectorMappings objectAtIndex:indexNum.integerValue];
// FIXME implement NSCopying protocol on vectorMaps and hand out copies !
[combinedMappings addObject:aMap];
}
return combinedMappings;
}
- (MusicStaffLineVectorMap *) findStaffVectorMapForNoteId:(kMusicNoteId)noteId
inOctave:(kMusicStaffOctave)oct
{
MusicStaffLineVectorMap *staffMapping = nil;
MusicStaffLineVectorMap *aMap;
for (NSNumber *indexNum in _loToHiCombinedMappingsIndex) {
aMap = [_staffVectorMappings objectAtIndex:indexNum.integerValue];
if (aMap.octaveInfo.octave == oct) {
if (aMap.octaveInfo.noteId == noteId) {
staffMapping = aMap;
break;
}
}
}
return staffMapping;
}
- (MusicStaffLineVectorMap *) findStaffVectorMapForNoteId:(kMusicNoteId)noteId
inOctave:(kMusicStaffOctave)oct
ofZoneType:(kMusicStaffZone)zoneType
{
MusicStaffLineVectorMap *staffMapping = nil;
MusicStaffLineVectorMap *aMap;
for (NSNumber *indexNum in _loToHiCombinedMappingsIndex) {
aMap = [_staffVectorMappings objectAtIndex:indexNum.integerValue];
if (aMap.octaveInfo.octave == oct) {
if (aMap.octaveInfo.noteId == noteId) {
if (aMap.zoneType == zoneType) {
staffMapping = aMap;
break;
}
}
}
}
return staffMapping;
}
#pragma mark - Find Staff Coordinate methods
- (CGPoint) findStaffCoordinateForNoteId:(kMusicNoteId)noteId
inOctave:(kMusicStaffOctave)oct
{
MusicStaffLineVectorMap *staffMapping = nil;
CGPoint nowhere = CGPointMake(0, 0);
int numStaffPositions = _staffLayoutInfo.usedStaffLines + _staffLayoutInfo.usedStaffSpaces;
// simple linear scan for now, very small array.
for (int i = 0; i < numStaffPositions; i++) {
staffMapping = [_staffVectorMappings objectAtIndex:i];
if (staffMapping.octaveInfo.octave == oct) {
if (staffMapping.octaveInfo.noteId == noteId) {
return staffMapping.coordinate;
}
}
}
return nowhere;
}
- (CGPoint) findStaffCoordinateForNoteUsingItsOctave:(NoteData *)aNote
{
SWFVector3 *staffVector = [self findStaffVectorForNoteUsingItsOctave:aNote];
return staffVector.pointFromVector;
}
- (kMusicStaffZone) findStaffZoneTypeForNoteId:(kMusicNoteId)noteId
inOctave:(kMusicStaffOctave)oct
{
kMusicStaffZone zoneType = kMusicStaffNOZone;
MusicStaffLineVectorMap *staffMapping = nil;
int numStaffPositions = _staffLayoutInfo.usedStaffLines + _staffLayoutInfo.usedStaffSpaces;
// simple linear scan for now, very small array.
for (int i = 0; i < numStaffPositions; i++) {
staffMapping = [_staffVectorMappings objectAtIndex:i];
if (staffMapping.octaveInfo.octave == oct) {
if (staffMapping.octaveInfo.noteId == noteId) {
zoneType = staffMapping.zoneType;
break;
}
}
}
return zoneType;
}
- (MusicStaffLineVectorMap *) findLowestLineVectorMap
{
if (_lowestLineVectorMapIndex > kMaxStaffLines) {
return nil;
}
MusicStaffLineVectorMap *staffMapping = [_staffVectorMappings objectAtIndex:_lowestLineVectorMapIndex];
return staffMapping;
}
- (MusicStaffLineVectorMap *) findHighestLineVectorMap
{
if (_highestLineVectorMapIndex > kMaxStaffLines) {
return nil;
}
MusicStaffLineVectorMap *staffMapping = [_staffVectorMappings objectAtIndex:_highestLineVectorMapIndex];
return staffMapping;
}
- (float) calcTopRealLineUsingMetrics
{
// FIXME DANGER this assumes only 1 stub will be below and above
CGFloat upperStub = _lowestYCoordinate - (6 * _staffMetrics.yGapBetweenLines);
// calc top lines Y offset, and round to land on half pixel
CGFloat topLine = upperStub + _staffMetrics.yGapBetweenLines;
if (_staffMetrics.staffLineWidth == 1) {
topLine = [MusicStaffRenderMetrics roundToLeastHalfPoint:topLine];
}
return topLine;
}
- (float) calcBottomRealLineUsingMetrics
{
// FIXME DANGER
return _lowestYCoordinate - _staffMetrics.yGapBetweenLines;
}
- (BOOL) isYCoordinateAboveTopStaffLine:(float)proposedY
{
if (proposedY < [self calcTopRealLineUsingMetrics]) {
return YES;
}
return NO;
}
- (BOOL) isYCoordinateBelowBottomStaffLine:(float)proposedY
{
if (proposedY > [self calcBottomRealLineUsingMetrics]) {
return YES;
}
return NO;
}
#pragma mark - MusicStaffDisplayable Protocol Methods
- (SWFVector3 *) middleCVector
{
return _middleCVector;
}
- (SWFVector3 *) findStaffVertexForNoteId:(kMusicNoteId)noteId inOctave:(kMusicStaffOctave)oct
{
MusicStaffLineVectorMap *staffMapping = nil;
int numStaffPositions = _staffLayoutInfo.usedStaffLines + _staffLayoutInfo.usedStaffSpaces;
// simple linear scan for now, very small array.
for (int i = 0; i < numStaffPositions; i++) {
staffMapping = [_staffVectorMappings objectAtIndex:i];
if (staffMapping.octaveInfo.octave == oct) {
if (staffMapping.octaveInfo.noteId == noteId) {
return staffMapping.vec;
}
}
}
return nil;
}
- (SWFVector3 *) findStaffVectorForNoteUsingItsOctave:(NoteData *)aNote
{
MusicStaffLineVectorMap *staffMapping;
SWFVector3 *staffVector = nil;
int numOctaves;
float noteGap = [self distanceBetweenStaffLines] / 2.0;
for (NSNumber *indexNum in _loToHiCombinedMappingsIndex) {
staffMapping = [_staffVectorMappings objectAtIndex:indexNum.integerValue];
if (aNote.noteId.integerValue == staffMapping.octaveInfo.noteId) {
staffVector = [staffMapping.vec mutableCopy];
// simple case note matches found staff location
if (aNote.octave.integerValue == staffMapping.octaveInfo.octave) {
break;
}
// Adjust position factor diff of chordNotes octave and staff note's octave
if (aNote.octave.integerValue > staffMapping.octaveInfo.octave) {
numOctaves = aNote.octave.intValue - staffMapping.octaveInfo.octave;
staffVector.y -= ((numOctaves * kNotesPerOctave) * noteGap);
// NSLog(@"Staff adj Y coord for noteId: %d to >: %.2f NoteOct: %d",
// aNote.noteId.intValue, staffVector.y, aNote.octave.integerValue);
break;
} else if (aNote.octave.integerValue < staffMapping.octaveInfo.octave) {
numOctaves = staffMapping.octaveInfo.octave - aNote.octave.intValue;
staffVector.y += ((numOctaves * kNotesPerOctave) * noteGap);
NSLog(@"Staff adj Y coord for noteId: %d to <: %.2f NoteOct: %ld",
aNote.noteId.intValue, staffVector.y, aNote.octave.longValue);
break;
}
}
}
return staffVector;
}
- (kMusicStaffOctave) nearestOctaveForNote:(kMusicNoteId)noteId andVertex:(SWFVector3 *)vec
{
kMusicStaffOctave oct = kOctaveNone;
MusicStaffDropZone *mdz;
// start at bottom zone and work up
unsigned count = 0;
while (count < _dropZones.count) {
mdz = [_dropZones objectAtIndex:count];
SWFBoundingBox zbb = mdz.boundingZone;
if (vec.y <= zbb.upr_y && vec.y >= zbb.llr_y) {
if (vec.x >= zbb.llr_x && vec.x <= zbb.upr_x) {
// cheap hit OR go after 'Z' too
if (vec.z >= zbb.llr_z && vec.z <= zbb.upr_z) {
oct = mdz.octave;
break;
}
}
}
count++;
}
if (oct == kOctaveNone) {
NSLog(@"Music Staff returning OCTAVE NONE for noteId: %ld", (long)noteId);
}
return oct;
}
- (MusicStaffDropZone *) nearestDropZoneFromVector:(SWFVector3 *)vec
usingGap:(kDropZoneGap)dzGap
{
MusicStaffDropZone *mdz = nil;
kDropStatus hitStatus = kDropStatus_None;
SWFVector3 *targetVec = vec;
float gapFudge = _staffMetrics.yGapBetweenLines / (float)dzGap;
// NSLog(@"MusicStaff nearestZone using GAP: %f", gapFudge);
unsigned count = (unsigned)[_dropZones count];
while (count--) {
mdz = [_dropZones objectAtIndex:count];
hitStatus = [mdz detectHitFromVector:targetVec gapAllowance:gapFudge];
if (hitStatus == kDropStatus_DirectHit || hitStatus == kDropStatus_NearZone) {
break;
}
mdz = nil;
}
return mdz;
}
- (float) distanceBetweenStaffLines
{
return _staffMetrics.yGapBetweenLines;
}
- (SWFVector3 *) lowestStaffPositionVector
{
if (_lowestLineVector && _lowestSpaceVector) {
if (_lowestLineVector.y < _lowestSpaceVector.y) {
return _lowestLineVector;
} else {
return _lowestSpaceVector;
}
}
return nil;
}
- (NSArray *) allDropZones
{
return self.dropZones;
}
- (NSArray *) measureBarCoordinatesForLine
{
return [NSArray arrayWithArray:_verticalBarCoordinates];
}
#pragma mark - Drop Zone Methods
- (void) calcDropZonesFromCombinedVectorMappings
{
MusicStaffLineVectorMap *staffMapping = nil;
MusicStaffDropZone *last1back = nil;
MusicStaffDropZone *dz;
[_dropZones removeAllObjects];
_currZoneHilite = nil;
// calc the uniform height of all drop zones
float zoneHeight = [self calcUniformDropZoneHeightBasedOnYGap];
// update the members consulted for swipe up and boundary dimensions
[self establishLeftAndRightXBoundaryFromMetrics];
// accessing the _staffVectorMappings thru the combined indexing array
// delivers staff positions in sequence starting at the lowest Space of staff
NSInteger dropZoneId = 1;
for (NSNumber *indexOffset in _loToHiCombinedMappingsIndex) {
staffMapping = [_staffVectorMappings objectAtIndex:indexOffset.integerValue];
if (staffMapping) {
// calc a bounding rect based on 1/2 zoneHeight either side of the
// 'Y' postion of staffMapping of given line or space
SWFVector3 *mappingVector = staffMapping.vec;
float zoneTop = mappingVector.y - (zoneHeight / 2.);
float zoneBottom = mappingVector.y + (zoneHeight / 2.);
SWFBoundingBox boundingBox;
boundingBox.llr_x = _leftmostX;
boundingBox.llr_y = zoneBottom;
boundingBox.llr_z = 0;
boundingBox.upr_x = _rightmostX;
boundingBox.upr_y = zoneTop;
boundingBox.upr_z = 0;
dz = [[MusicStaffDropZone alloc] initWithBoundingBox:&boundingBox
andType:staffMapping.zoneType];
dz.noteId = staffMapping.octaveInfo.noteId;
dz.octave = staffMapping.octaveInfo.octave;
dz.staffId = _staffLayoutInfo.staffId;
// do the linked list thing
if (indexOffset.integerValue == 0) {
last1back = dz;
} else {
dz.previousZone = last1back;
last1back.nextZone = dz;
last1back = dz;
}
dz.zoneId = dropZoneId;
[self addDropZone:dz];
dropZoneId++;
}
}
// NSLog(@"MyStaff DropZones--> %@", [self dropZoneDescriptions]);
}
- (void) establishLeftAndRightXBoundaryFromMetrics
{
_leftmostX = _staffMetrics.leftMostXOffset;
_rightmostX = _leftmostX;
_rightmostX += (_staffMetrics.numberOfMeasures *
_staffMetrics.lineLengthOneMeasure);
}
- (float) calcUniformDropZoneHeightBasedOnYGap
{
float spaceBetweenLines = _staffMetrics.yGapBetweenLines;
float zoneHeight = (spaceBetweenLines * _staffMetrics.dropZonePercentage);
zoneHeight = [MusicStaffRenderMetrics roundToLeastHalfPoint:zoneHeight];
return zoneHeight;
}
- (void) addDropZone:(id<SWFDropZone_p>)dropZone
{
[self.dropZones addObject:dropZone];
}
- (void) clearDropZones
{
[self.dropZones removeAllObjects];
}
- (BOOL) doDropZoneSetsMatch:(NSArray *)zoneSet1 secondSet:(NSArray *)zoneSet2
{
BOOL setsEqual = NO;
if (zoneSet1.count != zoneSet2.count) {
return NO;
}
MusicStaffDropZone *zoneA;
MusicStaffDropZone *zoneB;
setsEqual = YES;
for (int i = 0; i < zoneSet1.count; i++) {
zoneA = [zoneSet1 objectAtIndex:i];
zoneB = [zoneSet2 objectAtIndex:i];
// currently just look at noteId's
if (zoneA.noteId == zoneB.noteId) {
setsEqual = YES;
#ifdef MORE_DETAILED_MATCHING_NOT_YET_NEEDED
if (zoneA.octave == zoneB.octave) {
if (zoneA.center.y == zoneB.center.y) {
continue;
} else {
setsEqual = NO;
break;
}
} else {
setsEqual = NO;
break;
}
#endif
} else {
setsEqual = NO;
break;
}
}
return setsEqual;
}
#pragma mark - SWFDroppable Protocol Methods
- (id<SWFDropZone_p>) queryDropZoneAt:(float)x y:(float)y z:(float)z
{
MusicStaffDropZone *retZone = nil;
SWFVector3 *targetVec = [SWFVector3 vectorFromValues:x y:y z:z];
// Being a little forgiving here as point-perfect direct hits for
// drag an drop are a little much to ask of users
retZone = [self nearestDropZoneFromVector:targetVec
usingGap:kDropZoneGap_ThirdSpace];
return retZone;
}
- (SWFDropReply *) dropRequest:(SWFDropRequest *)dr
{
MusicStaffDropRequest *dropRequest = (MusicStaffDropRequest *)dr;
if ([dropRequest isGroupRequest]) {
return [self handleGroupDropRequest:dr];
} else {
return [self handleSoloDropRequest:dr];
}
}
- (SWFDropReply *) handleSoloDropRequest:(SWFDropRequest *)dr
{
MusicStaffDropReply *reply = nil;
MusicStaffDropRequest *dropRequest = (MusicStaffDropRequest *)dr;
MusicStaffDropZone *mdz;
bool hitZone = NO;
kDropStatus hitStatus = kDropStatus_None;
SWFVector3 *requestPos = dropRequest.requestPos;
mdz = (MusicStaffDropZone *)[self queryDropZoneAt:requestPos.x y:requestPos.y z:requestPos.z];
if (mdz != nil) {
hitZone = YES;
hitStatus = kDropStatus_DirectHit;
}
// unhilite prev zone if its not same as this (potential) hit
if (_currZoneHilite != nil) {
if (mdz == nil || ((mdz != nil) && (_currZoneHilite != mdz)) ) {
[self unHiliteZone:_currZoneHilite];
_currZoneHilite = nil;
}
}
// take care of no hit
if (!hitZone) {
// unhilite any previsouly lit zone
if (_currZoneHilite != nil) {
[self unHiliteZone:_currZoneHilite];
_currZoneHilite = nil;
}
// inform Draggable no hit detected on valid dropZone
reply = [[MusicStaffDropReply alloc] initWith:kDropStatus_None
andOrigRequest:dropRequest];
reply.staffId = self.staffId;
} else {
// Hilite the new hit zone
if (_currZoneHilite == nil) {
[self hiliteZone:mdz];
_currZoneHilite = mdz;
}
// simple Rules Passage, either direct or near hit a valid dropzone
reply = [[MusicStaffDropReply alloc] initWith:hitStatus
andOrigRequest:dropRequest
andHitZone:mdz
andNoteId:mdz.noteId];
reply.staffId = self.staffId;
}
return reply;
}
- (SWFDropReply *) handleGroupDropRequest:(SWFDropRequest *)dr
{
MusicStaffDropReply *reply = nil;
MusicStaffDropRequest *dropRequest = (MusicStaffDropRequest *)dr;
MusicStaffDropZone *mdz;
kDropStatus hitStatus = kDropStatus_None;
NSArray *acceptableHiliteZones = nil;
NSMutableArray *hitZones = [[NSMutableArray alloc] init];
BOOL allZonesMatch = NO;
NSArray *arrayOfPos = dropRequest.arrayPos;
for (int i = 0; i < arrayOfPos.count; i++) {
SWFVector3 *vec = [arrayOfPos objectAtIndex:i];
float stupidX = vec.x, stupidY = vec.y, stupidZ = vec.z;
mdz = (MusicStaffDropZone *)[self queryDropZoneAt:stupidX y:stupidY z:stupidZ];
if (mdz != nil) {
[hitZones addObject:mdz];
// FIXME Consider producing an array of hitZone status's, one for each vec
hitStatus = kDropStatus_DirectHit;
}
}
if (hitZones.count == arrayOfPos.count) {
if (self.multiZoneHilite == nil) {
// only do hiliting of multi zones if request supplied matching zoneIds
acceptableHiliteZones = dropRequest.acceptableHiliteZones;
if ([self doDropZoneSetsMatch:hitZones secondSet:acceptableHiliteZones]) {
[self hiliteMultiZones:hitZones];
_multiZoneHilite = hitZones;
allZonesMatch = YES;
NSLog(@"MusicStaffId %d All Zones MATCH:\n %@", self.staffId, hitZones);
}
} else {
// verify that existing hilites may stay that way
acceptableHiliteZones = dropRequest.acceptableHiliteZones;
if ([self doDropZoneSetsMatch:hitZones secondSet:acceptableHiliteZones]) {
allZonesMatch = YES;
}
}
// only if hits on all requested zones return a happy Reply
if (allZonesMatch) {
reply = [[MusicStaffDropReply alloc] initWith:hitStatus
andOrigRequest:dropRequest
andZonesArray:hitZones];
reply.staffId = self.staffId;
return reply;
}
} else if (hitZones.count > 0) {
NSLog(@"MusicStaffId %d PARTIAL Zone Match: %d", self.staffId, hitZones.count);
}
// else
{
// unHilite previous set of multiZones
if (self.multiZoneHilite != nil) {
[self unHiliteMultiZones:_multiZoneHilite];
[_multiZoneHilite removeAllObjects];
_multiZoneHilite = nil;
}
// inform Draggable no hit detected on any valid dropZone
reply = [[MusicStaffDropReply alloc] initWith:kDropStatus_None
andOrigRequest:dropRequest];
reply.staffId = self.staffId;
}
return reply;
}
- (void) dropComplete:(SWFDropReply *)dropReply
{
// MusicStaffDropReply *mdr = (MusicStaffDropReply *)dropReply;
// unhilite previsouly lit zone
if (_currZoneHilite != nil) {
[self unHiliteZone:_currZoneHilite];
_currZoneHilite = nil;
}
}
- (void) cancelAllRequests
{
// unhilite any previsouly lit zone
if (_currZoneHilite != nil) {
[self unHiliteZone:_currZoneHilite];
_currZoneHilite = nil;
}
if (self.multiZoneHilite != nil) {
for (MusicStaffDropZone *mdz in self.multiZoneHilite) {
[self unHiliteZone:mdz];
}
[self.multiZoneHilite removeAllObjects];
self.multiZoneHilite = nil;
}
}
- (NSString *) dropZoneDescriptions
{
NSMutableString *mutString = [NSMutableString string];
for (MusicStaffDropZone *z in _dropZones) {
NSString *firstLine = [NSString stringWithFormat:@"ZoneId: %d staffId: %d noteId: %d",
z.zoneId, z.staffId, z.noteId];
NSString *secondLine = [NSString stringWithFormat:@"ZoneCenter: %@ boundingBox: %@",
z.center,
NSStringFromCGRect(z.boundingRect)];
[mutString appendFormat:@"\n %@ %@", firstLine, secondLine];
}
return mutString;
}
- (void) hilightOptions:(kDropHilite)clue
{
}
#pragma mark - HiLight UnHilight workers call Delegate Notifcation Protocol
- (void) hiliteMultiZones:(NSArray *)zonesArray
{
for (MusicStaffDropZone *mdz in zonesArray) {
[self hiliteZone:mdz];
}
}
- (void) unHiliteMultiZones:(NSArray *)zonesArray
{
for (MusicStaffDropZone *mdz in zonesArray) {
[self unHiliteZone:mdz];
}
}
- (void) hiliteZone:(MusicStaffDropZone *)dz
{
if (_staffDelegate)
[_staffDelegate musicStaff:self willHiliteZone:dz];
#ifdef NO_HILIGHTING_IN_THIS_STAFF
if (dz.type == kMusicSpaceZone) {
[dz.swfMesh.isglMeshNode setMaterial:_hiliteSpaceMaterial];
dz.swfMesh.isglMeshNode.alpha = 0.6;
} else if (dz.type == kMusicLineZone) {
[dz.swfMesh.isglMeshNode setMaterial:_hiliteLineMaterial];
}
// FIXME toggle x or -x depending on users finger
// display hint note leter
_hintLetterOrigPos = _hintLetters[dz.noteId].position;
_hintLetters[dz.noteId].position = iv3(-2.0, dz.swfMesh.meshCenter.y, _hintLetterOrigPos.z);
#endif
}
- (void) unHiliteZone:(MusicStaffDropZone *)dz
{
if (_staffDelegate)
[_staffDelegate musicStaff:self willUnHiliteZone:dz];
#ifdef NO_HILIGHTING_IN_THIS_STAFF
// call into the wrapped Isgl mesh
if (dz.type == kMusicSpaceZone) {
[dz.swfMesh.isglMeshNode setMaterial:_spaceMaterial];
dz.swfMesh.isglMeshNode.alpha = 0.03;
} else if (dz.type == kMusicLineZone) {
[dz.swfMesh.isglMeshNode setMaterial:_lineMaterial];
}
// pull hint note letter out of view
_hintLetters[dz.noteId].position = iv3(-20.0, _hintLetterOrigPos.y, _hintLetterOrigPos.z);
#endif
}
- (NSDictionary *) textAttributesDictionaryForClef
{
NSMutableDictionary *attrsDict = [NSMutableDictionary dictionary];
if (_staffMetrics.musicSymbolsUnicodeFont != nil) {
attrsDict[NSFontAttributeName] = _staffMetrics.musicSymbolsUnicodeFont;
// ios defaults to horizontal, here we set to vertical for the music symbols
attrsDict[NSVerticalGlyphFormAttributeName] = @(1);
} else {
// Exit if No Font supplied !!!!
return nil;
}
if (_staffMetrics.clefAlphaComponent > 0. && _staffMetrics.clefAlphaComponent < 1.0) {
attrsDict[NSForegroundColorAttributeName] = [_staffMetrics.lineColor colorWithAlphaComponent:_staffMetrics.clefAlphaComponent];
} else if (_staffMetrics.clefAlphaComponent == 1.0) {
attrsDict[NSForegroundColorAttributeName] = _staffMetrics.lineColor;
}
return attrsDict;
}
- (void) pushContextIfEmpty:(CGContextRef)ctx
{
if (CGContextIsPathEmpty(ctx)) {
UIGraphicsPushContext(ctx);
} else {
NSLog(@"Staff ContextPathNOT Empty");
}
}
- (void) popContextIfEmpty:(CGContextRef)ctx
{
if (CGContextIsPathEmpty(ctx)) {
UIGraphicsPopContext();
}
}
#pragma mark - Draw Rendering Methods
- (void) drawStaffInContext:(CGContextRef)ctx withMeasureCount:(int)measureCount
{
CGFloat leftX = _staffMetrics.leftMostXOffset;
CGFloat rightX = leftX + (measureCount * _staffMetrics.lineLengthOneMeasure);
CGFloat lineGap = _staffMetrics.yGapBetweenLines;
// UIGraphicsPushContext(ctx);
[self pushContextIfEmpty:ctx];
CGContextSetStrokeColorWithColor(ctx, _staffMetrics.lineColor.CGColor);
CGContextSetLineWidth(ctx, _staffMetrics.staffLineWidth);
// DANGER this assumes only 1 stub will be below and above
CGFloat upperStubY = _lowestYCoordinate - (6 * lineGap);
// calc top lines Y offset, and round to land on half pixel
CGFloat firstY = upperStubY + lineGap;
if (_staffMetrics.staffLineWidth == 1) {
firstY = [MusicStaffRenderMetrics roundToLeastHalfPoint:firstY];
}
// FIXME ? these may need conditional half pixel tweaks (or whole pixel)
CGFloat barLineTopY = firstY - (_staffMetrics.staffLineWidth / 2);
CGFloat barLineBottomY = firstY + (_staffMetrics.staffLineWidth / 2);
int numFixedLines = _staffMetrics.fixedLineCount; // 5;
for (int x=0; x < numFixedLines; x++) {
CGContextMoveToPoint(ctx, leftX, firstY);
CGContextAddLineToPoint(ctx, rightX, firstY);
CGContextStrokePath(ctx);
firstY += lineGap;
if (x < (numFixedLines - 1)) {
barLineBottomY += lineGap;
}
}
// UIGraphicsPopContext();
[self popContextIfEmpty:ctx];
// now draw the vertical bar lines, optionally skipping last for open bar
[_verticalBarCoordinates removeAllObjects];
SWFVector3 *barVec;
BOOL skipBarLine = NO;
int barCount = measureCount + 1;
float barX = _staffMetrics.leftMostXOffset;
float measureLength = _staffMetrics.lineLengthOneMeasure;
// UIGraphicsPushContext(ctx);
[self pushContextIfEmpty:ctx];
CGContextSetStrokeColorWithColor(ctx, _staffMetrics.lineColor.CGColor);
for (int z = 0; z < barCount; z++) {
if (z == 0) {
CGContextSetLineWidth(ctx, _staffMetrics.firstMeasureBarLineWidth);
skipBarLine = _staffMetrics.skipFirstMeasureBarLine;
} else {
CGContextSetLineWidth(ctx, _staffMetrics.measureBarLineWidth);
}
// draw vertical bars, ...but sometimes don't draw the last bar (guitar TAB)
if (skipBarLine == NO) {
if (((_staffMetrics.lastMeasureOpenBar == YES) && (z + 1 < barCount)) ||
(_staffMetrics.lastMeasureOpenBar == NO)) {
CGContextMoveToPoint(ctx, barX, barLineTopY);
CGContextAddLineToPoint(ctx, barX, barLineBottomY);
CGContextStrokePath(ctx);
}
}
// stash their locations for further use
barVec = [SWFVector3 vectorFromValues:barX y:barLineTopY z:barLineBottomY];
[_verticalBarCoordinates addObject:barVec];
barX += measureLength;
skipBarLine = NO;
}
// update properties for drawing down the call stack
_topLineY = barLineTopY;
_bottomLineY = barLineBottomY;
CGContextSetLineWidth(ctx, _staffMetrics.staffLineWidth);
// UIGraphicsPopContext();
[self popContextIfEmpty:ctx];
}
#pragma mark - Clef Sign Calculations
- (CGRect) boundingRectForClef
{
NSString *clefString = [[MusicNamesFormatter sharedInstance] clefNameForId:self.clefId];
NSDictionary *attrsDict = [self textAttributesDictionaryForClef];
if (clefString == nil || attrsDict == nil) {
return CGRectZero;
}
CGPoint ptOrigin = CGPointMake(_staffMetrics.leftMostXOffset, _topLineY);
CGRect textRect = [self boundingRectForClefString:clefString
usingAttrs:attrsDict atOrigin:ptOrigin];
return textRect;
}
- (CGRect) boundingRectForClefString:(NSString *)clefString
usingAttrs:(NSDictionary *)attrsDict atOrigin:(CGPoint)ptOrigin
{
if (clefString == nil || attrsDict == nil) {
return CGRectZero;
}
CGRect textRect = CGRectZero;
// obtain bounding box for first glyph in surrogate pair codepoints,
// used for left/top/height
CGRect bbBox = [self getBoundingRectForGlyphFromString:clefString
withFont:attrsDict[NSFontAttributeName]];
CGFloat glyphLeftOffset = bbBox.origin.x;
if (glyphLeftOffset < 0) {
glyphLeftOffset = fabsf(glyphLeftOffset);
}
// If the glyph is positioned 'down' e.g. positive Y, then negate that
CGFloat glyphTopOffset = 0;
if (bbBox.origin.y > 0) {
glyphTopOffset = bbBox.origin.y * -1;
glyphTopOffset = [MusicStaffRenderMetrics roundToLeastHalfPoint:glyphTopOffset];
}
// position top/left account for any left negative bearing, or top positive
textRect.origin.x = (ptOrigin.x + glyphLeftOffset + _staffMetrics.measureBarLineWidth);
textRect.origin.x = [MusicStaffRenderMetrics roundToLeastHalfPoint:textRect.origin.x];
textRect.origin.y = ptOrigin.y + glyphTopOffset + _staffMetrics.staffLineWidth;
// can only rely on the .height being accurate, width is incorrect at 1
CGSize strSize = [clefString sizeWithAttributes:attrsDict];
textRect.size.height = ceilf(strSize.height);
textRect.size.width = ceilf(bbBox.size.width);
return textRect;
}
- (CGPoint) glyphOffsetFromString:(NSString *)glyphString withFont:(UIFont *)fnt
{
// obtain bounding box for first glyph in surrogate pair codepoints
CGRect bbBox = [self getBoundingRectForGlyphFromString:glyphString
withFont:fnt];
CGFloat glyphLeftOffset = bbBox.origin.x;
if (glyphLeftOffset < 0) {
glyphLeftOffset = fabsf((float)glyphLeftOffset);
}
glyphLeftOffset = [MusicStaffRenderMetrics roundToLeastHalfPoint:glyphLeftOffset];
// If the glyph is positioned 'down' e.g. positive Y, then negate that
CGFloat glyphTopOffset = 0;
if (bbBox.origin.y > 0) {
glyphTopOffset = bbBox.origin.y * -1;
glyphTopOffset = [MusicStaffRenderMetrics roundToLeastHalfPoint:glyphTopOffset];
}
return CGPointMake(glyphLeftOffset, glyphTopOffset);
}
- (CGRect) getBoundingRectForGlyphFromString:(NSString *)string withFont:(UIFont *)fnt
{
CGRect bbRectFirstGlyph;
// get characters from NSString
NSUInteger len = [string length];
UniChar *characters = (UniChar *)malloc(sizeof(UniChar)*len);
CFStringGetCharacters((__bridge CFStringRef)string, CFRangeMake(0, [string length]), characters);
CTFontRef coreTextFont = CTFontCreateWithName((CFStringRef)fnt.fontName, fnt.pointSize, NULL);
// allocate glyphs and bounding box arrays for holding the result
// assuming that each character is only one glyph, which is wrong
CGGlyph *glyphs = (CGGlyph *)malloc(sizeof(CGGlyph)*len);
CTFontGetGlyphsForCharacters(coreTextFont, characters, glyphs, len);
// get bounding boxes for glyphs
CGRect *bb = (CGRect *)malloc(sizeof(CGRect)*len);
CTFontGetBoundingRectsForGlyphs(coreTextFont, kCTFontDefaultOrientation, glyphs, bb, len);
// only interested in first glyph of surrogate pair that identify Clefs
bbRectFirstGlyph = bb[0];
CFRelease(coreTextFont);
free(characters);
free(glyphs);
free(bb);
return bbRectFirstGlyph;
}
#pragma mark - Draw Clef
- (void) drawClefOnStaffInContext:(CGContextRef)ctx
{
NSString *clefString = [[MusicNamesFormatter sharedInstance] clefNameForId:self.clefId];
NSDictionary *attrsDict = [self textAttributesDictionaryForClef];
if (clefString == nil || attrsDict == nil) {
return;
}
CGPoint ptOrigin = CGPointMake(_staffMetrics.leftMostXOffset, _topLineY);
CGRect textRect = [self boundingRectForClefString:clefString
usingAttrs:attrsDict atOrigin:ptOrigin];
CGRect bbBox = [self boundingRectForClef];
NSLog(@"Clef: %@ bbBox: %@\n\t\t\t\ttextRect: %@", clefString, NSStringFromCGRect(bbBox), NSStringFromCGRect(textRect));
UIGraphicsPushContext(ctx);
[clefString drawInRect:textRect withAttributes:attrsDict];
UIGraphicsPopContext();
}
- (CGRect) getBoundingRectsForGlyphUsingPathWithString:(NSString *)string
andFont:(UIFont *)fnt
{
// get characters from NSString
NSUInteger len = [string length];
UniChar *characters = (UniChar *)malloc(sizeof(UniChar)*len);
CFStringGetCharacters((__bridge CFStringRef)string, CFRangeMake(0, [string length]), characters);
CTFontRef coreTextFont = CTFontCreateWithName((CFStringRef)fnt.fontName, fnt.pointSize, NULL);
// allocate glyphs and bounding box arrays for holding the result
// assuming that each character is only one glyph, which is wrong
CGGlyph *glyphs = (CGGlyph *)malloc(sizeof(CGGlyph)*len);
CTFontGetGlyphsForCharacters(coreTextFont, characters, glyphs, len);
CGPathRef glyphPath = CTFontCreatePathForGlyph(coreTextFont, glyphs[0], NULL);
CGRect rect = CGPathGetBoundingBox(glyphPath);
CFRelease(coreTextFont);
CGPathRelease(glyphPath);
free(characters);
free(glyphs);
return rect;
}
#pragma mark - Stub Lines
- (BOOL) notePositionNeedsStubLine:(SWFVector3 *)noteVec
{
BOOL needsStub = YES;
float proposedY = noteVec.pointFromVector.y;
float topRealY = [self calcTopRealLineUsingMetrics];
float bottomRealY = [self calcBottomRealLineUsingMetrics];
if (proposedY >= topRealY && proposedY <= bottomRealY) {
// nothing to do, proposed location is in staff main body
needsStub = NO;
}
return needsStub;
}
- (float) stubLineYCoordinateForNoteId:(kMusicNoteId)noteId
inOctave:(kMusicStaffOctave)octave
withNotePosition:(SWFVector3 *)noteVec
{
float stubY = -1.;
float proposedY = 0.;
MusicStaffLineVectorMap *staffVecMap = nil;
float spaceHalfLineCheck = [self distanceBetweenStaffLines] / 2.0;
if ([self notePositionNeedsStubLine:noteVec] == NO) {
// nothing to do, proposed location is in staff main body
return -1.;
}
kMusicStaffZone zoneForNote = [self findStaffZoneTypeForNoteId:noteId inOctave:octave];
if (zoneForNote == kMusicLineZone) {
// when note's center position matches lineZone, just use it
stubY = noteVec.y;
} else if (zoneForNote == kMusicSpaceZone) {
// for spaces ensure the stub line has room _underneath_ the note
staffVecMap = [self findStaffVectorMapForNoteId:noteId
inOctave:octave
ofZoneType:zoneForNote];
if (staffVecMap) {
// first test the center of the space zone
proposedY = staffVecMap.vec.y;
if ([self isYCoordinateAboveTopStaffLine:proposedY] == YES) {
// now ensure the proposed stub line Y coord is valid location above main staff body
proposedY += spaceHalfLineCheck;
if ([self isYCoordinateAboveTopStaffLine:proposedY] == NO) {
// must be the first space above staff, no need for stub here
return -1.;
}
// Attn: observe the Y coordinate assigned to the stub line is based
// from the _notes Vector_ rather than the staffVectorMapping. While
// in 'most' cases the two will be equal, in scenarios such as the
// Selection Halo, the noteVectors are relative to the Halo View. As such
// the stub line needs to be rendered relative to that notes local space.
// Under 'normal' staff rendering on music page the noteVector and
// staffVectorMap will be the same. This disjointed useage works
// because the MusicStaffLineVectorMap is
// also matched up with the given notes, noteId and octave.
stubY = noteVec.y + spaceHalfLineCheck; // proposedY;
} else if ([self isYCoordinateBelowBottomStaffLine:proposedY] == YES) {
// under the main staff, ensure adjusted stub location is below the main staff body
proposedY -= spaceHalfLineCheck;
if ([self isYCoordinateBelowBottomStaffLine:proposedY] == NO) {
// must be the first space below the staff, no need for stub here
return -1.;
}
// Attn: read the observation written in the above if() condition, applies here.
stubY = noteVec.y + spaceHalfLineCheck; // proposedY;
}
}
} else if (zoneForNote == kMusicStaffNOZone) {
// octave for proposed Note is off the staff's mapping vectors. Here a cheap stop-gap
// is just draw a stub line at the 'last' mapped line coordinate farthest from main staff
// body. Now determine if the unmapped octave is too far above or below the staff.
// look hi
staffVecMap = [self findHighestLineVectorMap];
if (octave >= staffVecMap.octaveInfo.octave) {
stubY = staffVecMap.vec.y;
} else {
// look low
staffVecMap = [self findLowestLineVectorMap];
if (octave <= staffVecMap.octaveInfo.octave) {
stubY = staffVecMap.vec.y;
} else {
// get out of town (extremely rare as in never)
return -1.;
}
}
}
return stubY;
}
- (void) drawStubLineInContext:(CGContextRef)ctx
withNotePosition:(SWFVector3 *)noteVec
andNoteGeometry:(SWFVector3 *)noteGeometry
forNoteId:(kMusicNoteId)noteId
inOctave:(kMusicStaffOctave)octave
{
// determine valid Y coordinate for stub line, a negative value means no stub possible
float stubY = [self stubLineYCoordinateForNoteId:noteId inOctave:octave withNotePosition:noteVec];
if (stubY < 0.) {
return;
}
CGPoint notePos = noteVec.pointFromVector;
// craft line length to be slightly longer than width of note
float stubLineLength = noteGeometry.x * 1.4;
float stubLeftX = notePos.x - (stubLineLength / 2);
// UIGraphicsPushContext(ctx);
[self pushContextIfEmpty:ctx];
CGContextSetStrokeColorWithColor(ctx, _staffMetrics.lineColor.CGColor);
CGContextSetLineWidth(ctx, _staffMetrics.staffLineWidth);
CGContextMoveToPoint(ctx, stubLeftX, stubY);
CGContextAddLineToPoint(ctx, stubLeftX + stubLineLength, stubY);
CGContextStrokePath(ctx);
// UIGraphicsPopContext();
[self popContextIfEmpty:ctx];
}
@end
//
// MusicStaffFactory.h
// MusicRendering
//
// Created by Michael J Albanese on 4/12/14.
// Copyright (c) 2014 Michael J Albanese. All rights reserved.
//
#import <Foundation/Foundation.h>
#import "MusicTheory.h"
@class MusicStaffRenderMetrics;
@class MusicPageRenderCriteria;
@protocol MusicStaffDisplayable_p;
/**
* Factory performs dynamic instantiation of all MusicStaff classes and derivatives.
* This is a convenience factory and it not required to obtain a MusicStaff, one
* could directly instantiate an object. However this factory is very useful by
* the 'Builders' hierarchy, as they churn through all the tracks in a song and
* are required to produce many different staff objects. For them this quasi-agnostic
* factory approach proves more straightforward than direct individual class creation.
*/
@interface MusicStaffFactory : NSObject
+ (instancetype) sharedInstance;
+ (void) clearInstance;
/**
* FIXME ...trim down the params here, perhaps use Dictionary with known/Public Keys
*/
- (id<MusicStaffDisplayable_p>) staffFromStaffId:(kSongStaffId)staffId
andLayout:(MusicStaffLayoutInfo *)layoutInfo
andMiddleXCoordinate:(float)middleX
andLowestYCoordinate:(float)lowY
andStaffMetrics:(MusicStaffRenderMetrics *)staffMetrics
andPageRenderCriteria:(MusicPageRenderCriteria *)renderCriteria;
@end
//
// MusicStaffFactory.m
// MusicRendering
//
// Created by Michael J Albanese on 4/12/14.
// Copyright (c) 2014 Michael J Albanese. All rights reserved.
//
#import "MusicDataModelPrimitiveDefinitions.h"
#import "MusicStaffRenderMetrics.h"
#import "MusicPageRenderCriteria.h"
#import "MusicStaff.h"
#import "MusicGuitarTabSixStaff.h"
#import "MusicStaffFactory.h"
static MusicStaffFactory *_instance;
// Internal names of the MusicStaff classes (or derivatives)
NSString *kStandardMusicStaffClass = @"MusicStaff";
NSString *kGuitarTabSixStaffClass = @"MusicGuitarTabSixStaff";
NSString *kGuitarTabSixFretStaffClass = @"MusicGuitarTabSixFretStaff";
@interface MusicStaffFactory ()
@property (strong, nonatomic) NSMutableDictionary *keysDictionary;
@property (strong, nonatomic) NSMutableDictionary *classNamesDictionary;
@end
@implementation MusicStaffFactory
+ (MusicStaffFactory *) sharedInstance
{
@synchronized (self) {
if (!_instance) {
_instance = [[MusicStaffFactory alloc] initSingleton];
}
}
return _instance;
}
+ (void) clearInstance
{
@synchronized (self) {
if (_instance != nil) {
[_instance->_keysDictionary removeAllObjects];
_instance->_keysDictionary = nil;
[_instance->_classNamesDictionary removeAllObjects];
_instance->_classNamesDictionary = nil;
_instance = nil;
}
}
}
#pragma mark - Instance Methods
- (id) initSingleton
{
if ((self = [super init])) {
[self buildClassNamesDictionary];
_keysDictionary = [[NSMutableDictionary alloc] init];
}
return self;
}
- (void) buildClassNamesDictionary
{
_classNamesDictionary = [NSMutableDictionary dictionary];
_classNamesDictionary[ @(kStaffTreble) ] = kStandardMusicStaffClass;
_classNamesDictionary[ @(kStaffBass) ] = kStandardMusicStaffClass;
_classNamesDictionary[ @(kStaffTABSix) ] = kGuitarTabSixStaffClass;
_classNamesDictionary[ @(kStaffTABSixFret) ] = kGuitarTabSixFretStaffClass;
}
- (NSString *) findMusicStaffClassFromId:(kSongStaffId)staffId
{
NSString *staffClassName = _classNamesDictionary[ @(staffId) ];
return staffClassName;
}
#pragma mark - Public Api
// - (MusicStaff *) staffFromStaffId:(kSongStaffId)staffId
- (id<MusicStaffDisplayable_p>) staffFromStaffId:(kSongStaffId)staffId
andLayout:(MusicStaffLayoutInfo *)layoutInfo
andMiddleXCoordinate:(float)middleX
andLowestYCoordinate:(float)lowY
andStaffMetrics:(MusicStaffRenderMetrics *)staffMetrics
andPageRenderCriteria:(MusicPageRenderCriteria *)renderCriteria
{
if (!kVALID_STAFF_ID(staffId) || layoutInfo == nil) { // || staffMetrics == nil || renderCriteria == nil) {
return nil;
}
MusicStaff *staffObject = nil;
NSString *staffClassName = [self findMusicStaffClassFromId:staffId];
if (staffClassName != nil) {
Class musicStaffClass = NSClassFromString (staffClassName);
id anInstance = [[musicStaffClass alloc] initWithLayoutInfo:layoutInfo
andMiddleXCoordinate:middleX
andLowestYCoordinate:lowY
andStaffMetrics:staffMetrics
andPageRenderCriteria:renderCriteria];
staffObject = anInstance;
}
return staffObject;
}
@end
//
// MusicStaffTraversable_p.h
// MusicRendering
//
// Created by Michael J Albanese on 4/24/14.
// Copyright (c) 2014 Michael J Albanese. All rights reserved.
//
#import <Foundation/Foundation.h>
/**
* Protocol adopted by those MusicStaff classes which offer traversing of
* their contents. There are other library classes which take advantage of this
* offering, and they will provide the application layer a higher level functionality
* such that the app would not need to access this protocol's offering.
*
* Some examples may be staff iterators, which provide searching and lookup
* of notes and/or locations on a staff.
*
* Bear in mind the return arrays may be far different depending on the Staff supplying
* them. A standard music staff holds very few notes, whereas a guitar Tab staff has
* no spaces. However a guitar Tab staff's lines, each hold a sub array of up to 24
* elements, each of which may hold a note and represents a fret on the instrument fretboard.
*
* These intracacies are hidden by the Iterators, who offer a much smoother and less detailed
* access for locating notes in a staff.
*
* @seealso MusicGuitarStringFretIterator
*/
@protocol MusicStaffTraversable_p <NSObject>
- (NSArray *)traversableLines;
- (NSArray *)traversableSpaces;
@end
//
// MusicTabStaffSingleFretMap.h
// MusicTheory
//
// Created by Michael J Albanese on 4/24/14.
// Copyright (c) 2014 Michael J Albanese. All rights reserved.
//
#import <Foundation/Foundation.h>
#import "MusicTheoryDefs.h"
@class MusicMidiNoteOctave;
/**
* Small class used to hold contents of a fret on a given string in
* the Tab Staff. Is mainly used internally by the TabStaff for its mappings. It
* is also possible to receive an instance of this class while using the
* custom Iterator to walk over a guitar Tab staff for notes, midi codes, etc.
* that live at a given fretboard fret.
*
* @seealso MusicGuitarStringFretIterator
*/
@interface MusicTabStaffSingleFretMap : NSObject <NSCopying>
@property (nonatomic) NSInteger fretNumber;
@property (nonatomic) kMusicGuitarString owningString;
@property (nonatomic) NSInteger midiCode;
@property (nonatomic) float frequency;
@property (nonatomic) kMusicNoteId noteId;
@property (nonatomic, copy) NSString *codedNoteName;
@property (nonatomic) NSInteger octave;
// @property (nonatomic) kMusicStaffOctave octave; <-- uncomment when coverted to kMusicStaffOctave
@property (nonatomic) kMusicAccidental resolvedAccidental;
@property (nonatomic) kNoteInterval resolvedInterval;
@property (nonatomic) kMusicNoteId resolvedNoteId;
@property (nonatomic) kScaleDegree resolvedScaleDegree;
@property (nonatomic) BOOL isResolved;
+ (instancetype) singleFretMapForOwningString:(kMusicGuitarString)owningString;
- (BOOL) matchesMidiNoteOctave:(MusicMidiNoteOctave *)midiNoteOctave;
@end
//
// MusicTabStaffSingleFretMap.m
// MusicTheory
//
// Created by Michael J Albanese on 4/24/14.
// Copyright (c) 2014 Michael J Albanese. All rights reserved.
//
#import "MusicMidiNoteOctave.h"
#import "MusicTabStaffSingleFretMap.h"
@implementation MusicTabStaffSingleFretMap
+ (instancetype) singleFretMapForOwningString:(kMusicGuitarString)owningString
{
return [[MusicTabStaffSingleFretMap alloc] initWithOwningString:owningString];
}
- (id) initWithOwningString:(kMusicGuitarString)owningStringNumber
{
if (self = [super init]) {
_owningString = owningStringNumber;
}
return self;
}
- (BOOL) matchesMidiNoteOctave:(MusicMidiNoteOctave *)midiNoteOctave
{
BOOL theyMatch = NO;
if (midiNoteOctave.isValidMidiCode) {
if (_midiCode == midiNoteOctave.midiCode) {
theyMatch = YES;
}
} else if (midiNoteOctave.isValidNote && midiNoteOctave.isValidOctave) {
if (midiNoteOctave.noteId == _noteId &&
midiNoteOctave.octave == _octave) {
theyMatch = YES;
}
}
return theyMatch;
}
#pragma mark - Copying
- (instancetype) copyWithZone:(NSZone *)zone
{
MusicTabStaffSingleFretMap *copyEnt;
copyEnt = [[MusicTabStaffSingleFretMap allocWithZone:zone]
initWithOwningString:self.owningString];
if (copyEnt) {
copyEnt.fretNumber = self.fretNumber;
copyEnt.midiCode = self.midiCode;
copyEnt.frequency = self.frequency;
copyEnt.noteId = self.noteId;
copyEnt.octave = self.octave;
copyEnt.codedNoteName = self.codedNoteName;
}
return copyEnt;
}
- (instancetype) mutableCopyWithZone:(NSZone *)zone
{
MusicTabStaffSingleFretMap *copyEnt;
copyEnt = [[MusicTabStaffSingleFretMap allocWithZone:zone]
initWithOwningString:self.owningString];
if (copyEnt) {
copyEnt.fretNumber = self.fretNumber;
copyEnt.midiCode = self.midiCode;
copyEnt.frequency = self.frequency;
copyEnt.noteId = self.noteId;
copyEnt.octave = self.octave;
copyEnt.codedNoteName = self.codedNoteName;
}
return copyEnt;
}
@end
//
// MusicTabStaffStringFretMidiMaps.h
// MusicTheory
//
// Created by Michael J Albanese on 4/21/14.
// Copyright (c) 2014 Michael J Albanese. All rights reserved.
//
#import <UIKit/UIKit.h>
#import <Foundation/Foundation.h>
#import "MusicTheoryDefs.h"
@class SWFVector3;
@class MusicMidiNoteFrequenciesPianoTable;
@class MusicTabStaffSingleFretMap;
/**
* Conceptually this is a String object which maps to one line on a Tab staff.
* Internally it holds an array of TabStaffSingleFretMap objects, one for each
* fret on the fretboard. A Tab staff generally holds an array of six (or four) of
* these string objets, and may at any time call upon the string object to 're-tune'
* all of its frets based on a given base midi code (e.g. the midi code for this string
* if it were to be played as open).
*
* @seealso MusicTabStaffSingleFretMap
*/
@interface MusicTabStaffStringFretMidiMaps : NSObject
@property (nonatomic) BOOL isSelected;
@property (nonatomic, readonly) BOOL isTuned;
@property (nonatomic) CGPoint coordinate;
@property (strong, nonatomic) SWFVector3 *vec;
@property (nonatomic) kMusicStaffZone zoneType;
@property (nonatomic, readonly) NSArray *allFretMaps;
@property (nonatomic, readonly) NSInteger countOfFretMaps;
@property (nonatomic, readonly) kMusicGuitarString stringNumber;
@property (nonatomic, readonly) MusicTabStaffSingleFretMap *fretMapForNut;
+ (instancetype) stringFretMidiMapForStringNumber:(kMusicGuitarString)stringNumber;
- (void) tuneAllFretsUsingNutMidiCode:(NSInteger)nutMidi
andFrequenciesTable:(MusicMidiNoteFrequenciesPianoTable *)midiTable;
/** caller uses 1 based fret numbers, even though zero is valid and identifies nut */
- (MusicTabStaffSingleFretMap *) fretMapForFretNumber:(NSInteger)fretNumber;
- (MusicTabStaffSingleFretMap *) fretMapForNut;
@end
//
// MusicTabStaffStringFretMidiMaps.m
// MusicTheory
//
// Created by Michael J Albanese on 4/21/14.
// Copyright (c) 2014 Michael J Albanese. All rights reserved.
//
#import "SWFVector3.h"
#import "MusicMidiNoteFrequenciesPianoTable.h"
#import "MusicMidiNoteFrequencyEntry.h"
#import "MusicTabStaffSingleFretMap.h"
#import "MusicTabStaffStringFretMidiMaps.h"
@interface MusicTabStaffStringFretMidiMaps ()
@property (strong, nonatomic) NSMutableArray *fretMaps;
@property (nonatomic, readwrite) kMusicGuitarString stringNumber;
@property (nonatomic, readwrite) BOOL isTuned;
@end
@implementation MusicTabStaffStringFretMidiMaps
+ (instancetype) stringFretMidiMapForStringNumber:(kMusicGuitarString)stringNumber
{
return [[MusicTabStaffStringFretMidiMaps alloc] initWithStringNumber:stringNumber];
}
- (id) initWithStringNumber:(kMusicGuitarString)stringNumber
{
if (self = [super init]) {
_stringNumber = stringNumber;
}
return self;
}
- (void) dealloc
{
[_fretMaps removeAllObjects];
}
- (CGPoint) coordinate
{
return _vec.pointFromVector;
}
- (NSInteger) countOfFretMaps
{
if (_fretMaps != nil) {
return _fretMaps.count;
}
return 0;
}
- (NSArray *) allFretMaps
{
if (!_isTuned) {
return nil;
}
return (NSArray *)_fretMaps;
}
- (void) setupSingleFretMap:(MusicTabStaffSingleFretMap *)aFretMap
fromMidiTableEntry:(MusicMidiNoteFrequencyEntry *)tableEntry
{
aFretMap.midiCode = tableEntry.midiCode;
aFretMap.frequency = tableEntry.frequency;
aFretMap.noteId = tableEntry.noteId;
aFretMap.octave = tableEntry.octave;
aFretMap.codedNoteName = tableEntry.codedNoteName;
}
- (void) tuneAllFretsUsingNutMidiCode:(NSInteger)nutMidi
andFrequenciesTable:(MusicMidiNoteFrequenciesPianoTable *)midiTable
{
MusicTabStaffSingleFretMap *aFretMap;
if (_fretMaps) {
[_fretMaps removeAllObjects];
} else {
_fretMaps = [NSMutableArray array];
}
MusicMidiNoteFrequencyEntry *tableEntry = [midiTable findEntryByMidiCode:nutMidi];
if (tableEntry == nil) {
NSAssert1(tableEntry != nil, @"Invalid Midi Code: %ld for nut given to tune String", (long)nutMidi);
return;
}
// do the Nut for this string first, place at element Zero
aFretMap = [MusicTabStaffSingleFretMap singleFretMapForOwningString:_stringNumber];
aFretMap.fretNumber = 0;
[self setupSingleFretMap:aFretMap fromMidiTableEntry:tableEntry];
[_fretMaps addObject:aFretMap];
// loop for all frets on string and build internal array
NSInteger nextMidiCode = nutMidi + 1;
for (NSInteger fretNumber = 1; fretNumber <= kTotalGuitarFretCount; fretNumber++) {
tableEntry = [midiTable findEntryByMidiCode:nextMidiCode];
if (tableEntry != nil) {
aFretMap = [MusicTabStaffSingleFretMap singleFretMapForOwningString:_stringNumber];
aFretMap.fretNumber = fretNumber;
[self setupSingleFretMap:aFretMap fromMidiTableEntry:tableEntry];
[_fretMaps addObject:aFretMap];
}
nextMidiCode++;
}
_isTuned = YES;
}
- (MusicTabStaffSingleFretMap *) fretMapForNut
{
if (_fretMaps == nil || !_isTuned) {
return nil;
}
MusicTabStaffSingleFretMap *oneFret = _fretMaps[0];
return oneFret;
}
- (MusicTabStaffSingleFretMap *) fretMapForFretNumber:(NSInteger)fretNumber
{
if (_fretMaps == nil || fretNumber > _fretMaps.count || fretNumber < 0 || !_isTuned) {
return nil;
}
MusicTabStaffSingleFretMap *oneFret = _fretMaps[fretNumber];
return oneFret;
}
@end
#import "MusicMidiNoteOctave.h"
#import "MusicGuitarStringFretRange.h"
#import "MusicTabStaffSingleFretMap.h"
#import "MusicTabStaffStringFretMidiMaps.h"
#import "MusicGuitarStringFretIterator.h"
@interface TestIterator : NSObject
@end
@implementation TestIterator
- (instancetype) int
{
if (!(self = [super init])) { return nil; }
return self;
}
- (MusicGuitarTabSixStaff *) fabricateTABStaff
{
MusicGuitarTabSixStaff *tabSixStaff;
MusicScoreTrackTemplateFactory *templFactory;
MusicScoreGuitarTabSixTemplate *guitarTabTemplate;
MusicStaffLayoutInfo tabStaffLayout = {0};
templFactory = [MusicScoreTrackTemplateFactory sharedInstance];
guitarTabTemplate = [templFactory guitarTabSixTemplateDefaultStaffLayouts];
tabStaffLayout = [guitarTabTemplate staffLayoutForStaffId:kStaffTABSix];
tabSixTaff = (MusicGuitarTabSixStaff *) [[MusicStaffFactory sharedInstance]
staffFromStaffId:kStaffTABSix
andLayout:&tabStaffLayout
andMiddleXCoordinate:0
andLowestYCoordinate:0
andStaffMetrics:nil
andPageRenderCriteria:nil];
return tabSixStaff;
}
- (void) testAminorChord
{
SongChordNoteType *chordNote1, *chordNote2, *chordNote3;
// define array of chord notes
chordNote1 = [SongChordNoteType chordNoteTypeWithId:kMusicNoteId_A];
chordNote1.octave = 3;
chordNote2 = [SongChordNoteType chordNoteTypeWithId:kMusicNoteId_E];
chordNote2.octave = 3;
chordNote3 = [SongChordNoteType chordNoteTypeWithId:kMusicNoteId_C];
chordNote3.octave = 4;
NSArray *notesArray = @[ chordNote1, chordNote2, chordNote3 ];
// define the FretRange for iteration
MusicGuitarStringFretRange *iterRange;
MusicGuitarStringFretLocation *lowerLocation, *upperLocation, *middleLocation;
upperLocation = [MusicGuitarStringFretLocation locationWithString:kMusicGuitarString_One
andFret:3];
lowerLocation = [MusicGuitarStringFretLocation locationWithString:kMusicGuitarString_Four
andFret:0];
middleLocation = [MusicGuitarStringFretLocation locationWithString:kMusicGuitarString_Three
andFret:2];
iterRange = [MusicGuitarStringFretRange stringFretRangeUsingBottom:lowerLocation
andTopLocation:upperLocation
andMiddleLocation:middleLocation];
iterRange.interimUpperFretLimit = 3;
// get a TAB staff, then acquire its 'lines' array
MusicGuitarTabSixStaff *tabSixStaff = [self fabricateTABStaff];
NSArray *arTabLines = [tabSixStaff traversableLines];
[self detectFretsForNotes:notesArray withinRange:iterRange usingTABLines:arTabLines];
}
- (void) detectFretsForNotes:(NSArray *)arrayOfNotes
withinRange:(MusicGuitarStringFretRange *)iterRange
usingTABLines:(NSArray *)tabLines
{
MusicTabStaffSingleFretMap *foundMap;
MusicGuitarStringFretIterator *iter;
MusicMidiNoteOctave *targetNote;
iter = [MusicGuitarStringFretIterator iteratorWithLinesArray:tabLines
andRange:iterRange];
BOOL firstFind = YES;
for (SongChordNoteType *aNote in arrayOfNotes) {
targetNote = [MusicMidiNoteOctave noteOctaveWithNote:aNote.noteId
andOctave:aNote.octave];
if (firstFind) {
foundMap = [iter findFirstMatchingNoteOctave:targetNote
startingFromRange:kSearchRange_MiddleRange
inDirection:kSearchDirection_Top
wrap:YES];
} else {
foundMap = [iter findUpMatchingNoteOctave:targetNote
wrap:YES];
}
if (foundMap) {
aNote.guitarString = foundMap.owningString;
aNote.guitarFret = foundMap.fretNumber;
aNote.midiCode = foundMap.midiCode;
}
firstFind = NO;
}
}
@end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment