Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Select an option

  • Save MervinPraison/3c0c67b56ebfaf9d284b3e1000757857 to your computer and use it in GitHub Desktop.

Select an option

Save MervinPraison/3c0c67b56ebfaf9d284b3e1000757857 to your computer and use it in GitHub Desktop.

Revisions

  1. @mjaSanJose mjaSanJose revised this gist Aug 15, 2016. 6 changed files with 697 additions and 7 deletions.
    370 changes: 370 additions & 0 deletions MidiCodeFrequencies.plist
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,370 @@
    <?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>
    30 changes: 30 additions & 0 deletions MusicMidiNoteFrequenciesPianoTable.h
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,30 @@
    //
    // 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
    226 changes: 226 additions & 0 deletions MusicMidiNoteFrequenciesPianoTable.m
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,226 @@
    //
    // 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









    29 changes: 29 additions & 0 deletions MusicMidiNoteFrequencyEntry.h
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,29 @@
    //
    // 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
    28 changes: 28 additions & 0 deletions MusicMidiNoteFrequencyEntry.m
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,28 @@
    //
    // 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
    21 changes: 14 additions & 7 deletions README.md
    Original file line number Diff line number Diff line change
    @@ -5,16 +5,23 @@ This gist has the source code to match the series of [Blog posts for mapping not
    Here is a brief rundown of the source files, and headers in this gist:

    ```
    MusicTabStaffSingleFretMap.hm - contains info for an individual guitar fret
    MusicTabStaffStringFretMidiMaps.hm - acts as the 'string' on TAB staff and holds
    a series of SingleFretMap's
    MusicGuitarTabSixStaff.hm - actual TAB staff, has six (or four) tuneable strings
    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
    MusicGuitarStringFretIterator.hm - Offers iteration over Tab staff / guitar fretboard
    TestIteration.m - example code for using the Iterator
    MidiCodeFrequencies.plist - plist file defines 4 arrays, notes, midi codes, etc.
    ```

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

  2. @mjaSanJose mjaSanJose revised this gist Aug 13, 2016. 7 changed files with 2356 additions and 0 deletions.
    45 changes: 45 additions & 0 deletions MusicGuitarTabSixStaff.h
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,45 @@
    //
    // 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
    571 changes: 571 additions & 0 deletions MusicGuitarTabSixStaff.m
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,571 @@
    //
    // 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
    143 changes: 143 additions & 0 deletions MusicStaff.h
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,143 @@
    //
    // 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

    1,424 changes: 1,424 additions & 0 deletions MusicStaff.m
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,1424 @@
    //
    // 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




    40 changes: 40 additions & 0 deletions MusicStaffFactory.h
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,40 @@
    //
    // 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


    112 changes: 112 additions & 0 deletions MusicStaffFactory.m
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,112 @@
    //
    // 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
    21 changes: 21 additions & 0 deletions TestIteration.m
    Original file line number Diff line number Diff line change
    @@ -18,6 +18,27 @@ - (instancetype) int
    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;
  3. @mjaSanJose mjaSanJose revised this gist Aug 6, 2016. 1 changed file with 138 additions and 0 deletions.
    138 changes: 138 additions & 0 deletions MusicTabStaffStringFretMidiMaps.m
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,138 @@
    //
    // 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
  4. @mjaSanJose mjaSanJose revised this gist Aug 5, 2016. 2 changed files with 0 additions and 20 deletions.
    20 changes: 0 additions & 20 deletions MappingGuitarFretboard.md
    Original file line number Diff line number Diff line change
    @@ -1,20 +0,0 @@
    ## Mapping Notes on a Guitar Fretboard

    This gist has the source code to match the series of [Blog posts for mapping notes on guitar](http://musicwritingcode.com/2016/07/31/mapping-notes-on-guitar/).

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

    ```
    MusicTabStaffSingleFretMap.hm - contains info for an individual guitar fret
    MusicTabStaffStringFretMidiMaps.hm - acts as the 'string' on TAB staff and holds
    a series of SingleFretMap's
    MusicGuitarTabSixStaff.hm - actual TAB staff, has six (or four) tuneable strings
    MusicGuitarStringFretLocation.hm - basic string-fret location class
    MusicGuitarStringFretRange.hm - defines range for controlling iterators fretboard position
    MusicMidiNoteOctave.hm - simple class, part of public Api to Iterator find methods
    MusicGuitarStringFretIterator.hm - Offers iteration over Tab staff / guitar fretboard
    TestIteration.m - example code for using the Iterator
    ```

    Being that this is a gist, these files will _not_ compile as is. They are part of a much larger project.

    Binary file removed halfheightGuitarTrans.png
    Binary file not shown.
  5. @mjaSanJose mjaSanJose revised this gist Aug 5, 2016. 12 changed files with 606 additions and 0 deletions.
    20 changes: 20 additions & 0 deletions MusicGuitarStringFretLocation.h
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,20 @@
    //
    // 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
    33 changes: 33 additions & 0 deletions MusicGuitarStringFretLocation.m
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,33 @@
    //
    // 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
    61 changes: 61 additions & 0 deletions MusicGuitarStringFretRange.h
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,61 @@
    //
    // 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




    35 changes: 35 additions & 0 deletions MusicGuitarStringFretRange.m
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,35 @@
    //
    // 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
    27 changes: 27 additions & 0 deletions MusicMidiNoteOctave.h
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,27 @@
    //
    // 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
    105 changes: 105 additions & 0 deletions MusicMidiNoteOctave.m
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,105 @@
    //
    // 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




    33 changes: 33 additions & 0 deletions MusicStaffTraversable_p.h
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,33 @@
    //
    // 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
    42 changes: 42 additions & 0 deletions MusicTabStaffSingleFretMap.h
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,42 @@
    //
    // 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


    88 changes: 88 additions & 0 deletions MusicTabStaffSingleFretMap.m
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,88 @@
    //
    // 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
    47 changes: 47 additions & 0 deletions MusicTabStaffStringFretMidiMaps.h
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,47 @@
    //
    // 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
    20 changes: 20 additions & 0 deletions README.md
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,20 @@
    ## Mapping Notes on a Guitar Fretboard

    This gist has the source code to match the series of [Blog posts for mapping notes on guitar](http://musicwritingcode.com/2016/07/31/mapping-notes-on-guitar/).

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

    ```
    MusicTabStaffSingleFretMap.hm - contains info for an individual guitar fret
    MusicTabStaffStringFretMidiMaps.hm - acts as the 'string' on TAB staff and holds
    a series of SingleFretMap's
    MusicGuitarTabSixStaff.hm - actual TAB staff, has six (or four) tuneable strings
    MusicGuitarStringFretLocation.hm - basic string-fret location class
    MusicGuitarStringFretRange.hm - defines range for controlling iterators fretboard position
    MusicMidiNoteOctave.hm - simple class, part of public Api to Iterator find methods
    MusicGuitarStringFretIterator.hm - Offers iteration over Tab staff / guitar fretboard
    TestIteration.m - example code for using the Iterator
    ```

    Being that this is a gist, these files will _not_ compile as is. They are part of a much larger project.

    95 changes: 95 additions & 0 deletions TestIteration.m
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,95 @@

    #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;
    }

    - (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
  6. @mjaSanJose mjaSanJose revised this gist Aug 5, 2016. 1 changed file with 7 additions and 7 deletions.
    14 changes: 7 additions & 7 deletions MappingGuitarFretboard.md
    Original file line number Diff line number Diff line change
    @@ -5,15 +5,15 @@ This gist has the source code to match the series of [Blog posts for mapping not
    Here is a brief rundown of the source files, and headers in this gist:

    ```
    MusicTabStaffSingleFretMap.hm - contains info for an individual guitar fret
    MusicTabStaffSingleFretMap.hm - contains info for an individual guitar fret
    MusicTabStaffStringFretMidiMaps.hm - acts as the 'string' on TAB staff and holds
    a series of SingleFretMap's
    MusicGuitarTabSixStaff.hm - actual TAB staff, has six (or four) tuneable strings
    MusicGuitarStringFretLocation.hm - basic string-fret location class
    MusicGuitarStringFretRange.hm - defines range for controlling iterators fretboard position
    MusicMidiNoteOctave.hm - simple class, part of public Api to Iterator find methods
    MusicGuitarStringFretIterator.hm - Offers iteration over Tab staff / guitar fretboard
    TestIteration.m - example code for using the Iterator
    MusicGuitarTabSixStaff.hm - actual TAB staff, has six (or four) tuneable strings
    MusicGuitarStringFretLocation.hm - basic string-fret location class
    MusicGuitarStringFretRange.hm - defines range for controlling iterators fretboard position
    MusicMidiNoteOctave.hm - simple class, part of public Api to Iterator find methods
    MusicGuitarStringFretIterator.hm - Offers iteration over Tab staff / guitar fretboard
    TestIteration.m - example code for using the Iterator
    ```

    Being that this is a gist, these files will _not_ compile as is. They are part of a much larger project.
  7. @mjaSanJose mjaSanJose revised this gist Aug 5, 2016. 1 changed file with 2 additions and 1 deletion.
    3 changes: 2 additions & 1 deletion MappingGuitarFretboard.md
    Original file line number Diff line number Diff line change
    @@ -4,6 +4,7 @@ This gist has the source code to match the series of [Blog posts for mapping not

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

    ```
    MusicTabStaffSingleFretMap.hm - contains info for an individual guitar fret
    MusicTabStaffStringFretMidiMaps.hm - acts as the 'string' on TAB staff and holds
    a series of SingleFretMap's
    @@ -13,7 +14,7 @@ MusicGuitarStringFretRange.hm - defines range for controlling iterators fretboar
    MusicMidiNoteOctave.hm - simple class, part of public Api to Iterator find methods
    MusicGuitarStringFretIterator.hm - Offers iteration over Tab staff / guitar fretboard
    TestIteration.m - example code for using the Iterator

    ```

    Being that this is a gist, these files will _not_ compile as is. They are part of a much larger project.

  8. @mjaSanJose mjaSanJose revised this gist Aug 5, 2016. 1 changed file with 0 additions and 2 deletions.
    2 changes: 0 additions & 2 deletions MappingGuitarFretboard.md
    Original file line number Diff line number Diff line change
    @@ -1,7 +1,5 @@
    ## Mapping Notes on a Guitar Fretboard

    ![](https://gist.github.com/mjaSanJose/46793d85d5bb7ddf3a7481a25b8569c/halfheightGuitarTrans.png)

    This gist has the source code to match the series of [Blog posts for mapping notes on guitar](http://musicwritingcode.com/2016/07/31/mapping-notes-on-guitar/).

    Here is a brief rundown of the source files, and headers in this gist:
  9. @mjaSanJose mjaSanJose revised this gist Aug 5, 2016. 1 changed file with 13 additions and 7 deletions.
    20 changes: 13 additions & 7 deletions MappingGuitarFretboard.md
    Original file line number Diff line number Diff line change
    @@ -2,14 +2,20 @@

    ![](https://gist.github.com/mjaSanJose/46793d85d5bb7ddf3a7481a25b8569c/halfheightGuitarTrans.png)

    Writing a music notation app has many challenges. Finding notes and formatting chords for Piano is somewhat straightforward. Doing so for a Guitar fretboard presents many difficult challenges.
    This gist has the source code to match the series of [Blog posts for mapping notes on guitar](http://musicwritingcode.com/2016/07/31/mapping-notes-on-guitar/).

    One such challenge is given a set of notes from a known music chord, write some code which will take those notes as input and iterate over the guitar fretboard to locate a reasonable set of guitar string/fret locations for each note. The string/fret locations may then be used to display a guitar 'Fret Diagram' of the given chord.
    Here is a brief rundown of the source files, and headers in this gist:

    Since this is code we would like it to be flexible, ideally taking in some additional information beyond the music chord.
    MusicTabStaffSingleFretMap.hm - contains info for an individual guitar fret
    MusicTabStaffStringFretMidiMaps.hm - acts as the 'string' on TAB staff and holds
    a series of SingleFretMap's
    MusicGuitarTabSixStaff.hm - actual TAB staff, has six (or four) tuneable strings
    MusicGuitarStringFretLocation.hm - basic string-fret location class
    MusicGuitarStringFretRange.hm - defines range for controlling iterators fretboard position
    MusicMidiNoteOctave.hm - simple class, part of public Api to Iterator find methods
    MusicGuitarStringFretIterator.hm - Offers iteration over Tab staff / guitar fretboard
    TestIteration.m - example code for using the Iterator

    * Given a music chord find all the string/fret locations for each note
    * Utilize 'Range' information to limit the Iterator to a fretboard section
    * Rely upon a listing that maps all 88 piano midi codes to their notes/octaves
    * (this will be needed to 'tune' the six strings on a guitar)

    Being that this is a gist, these files will _not_ compile as is. They are part of a much larger project.

  10. @mjaSanJose mjaSanJose revised this gist Jul 23, 2016. 1 changed file with 1 addition and 1 deletion.
    2 changes: 1 addition & 1 deletion MappingGuitarFretboard.md
    Original file line number Diff line number Diff line change
    @@ -1,6 +1,6 @@
    ## Mapping Notes on a Guitar Fretboard

    ![](halfheightGuitarTrans.png)
    ![](https://gist.github.com/mjaSanJose/46793d85d5bb7ddf3a7481a25b8569c/halfheightGuitarTrans.png)

    Writing a music notation app has many challenges. Finding notes and formatting chords for Piano is somewhat straightforward. Doing so for a Guitar fretboard presents many difficult challenges.

  11. @mjaSanJose mjaSanJose revised this gist Jul 23, 2016. 1 changed file with 1 addition and 1 deletion.
    2 changes: 1 addition & 1 deletion MappingGuitarFretboard.md
    Original file line number Diff line number Diff line change
    @@ -1,6 +1,6 @@
    ## Mapping Notes on a Guitar Fretboard

    ![fretboard](DocImages/halfheightGuitarTrans.png)
    ![](halfheightGuitarTrans.png)

    Writing a music notation app has many challenges. Finding notes and formatting chords for Piano is somewhat straightforward. Doing so for a Guitar fretboard presents many difficult challenges.

  12. @mjaSanJose mjaSanJose revised this gist Jul 23, 2016. 1 changed file with 0 additions and 0 deletions.
    Binary file removed DocImages/halfheightGuitarTrans.png
    Binary file not shown.
  13. @mjaSanJose mjaSanJose revised this gist Jul 23, 2016. 1 changed file with 0 additions and 0 deletions.
    Binary file added halfheightGuitarTrans.png
    Loading
    Sorry, something went wrong. Reload?
    Sorry, we cannot display this file.
    Sorry, this file is invalid so it cannot be displayed.
  14. @mjaSanJose mjaSanJose revised this gist Jul 23, 2016. 2 changed files with 2 additions and 0 deletions.
    Binary file added DocImages/halfheightGuitarTrans.png
    Loading
    Sorry, something went wrong. Reload?
    Sorry, we cannot display this file.
    Sorry, this file is invalid so it cannot be displayed.
    2 changes: 2 additions & 0 deletions MappingGuitarFretboard.md
    Original file line number Diff line number Diff line change
    @@ -1,5 +1,7 @@
    ## Mapping Notes on a Guitar Fretboard

    ![fretboard](DocImages/halfheightGuitarTrans.png)

    Writing a music notation app has many challenges. Finding notes and formatting chords for Piano is somewhat straightforward. Doing so for a Guitar fretboard presents many difficult challenges.

    One such challenge is given a set of notes from a known music chord, write some code which will take those notes as input and iterate over the guitar fretboard to locate a reasonable set of guitar string/fret locations for each note. The string/fret locations may then be used to display a guitar 'Fret Diagram' of the given chord.
  15. @mjaSanJose mjaSanJose created this gist Jul 23, 2016.
    13 changes: 13 additions & 0 deletions MappingGuitarFretboard.md
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,13 @@
    ## Mapping Notes on a Guitar Fretboard

    Writing a music notation app has many challenges. Finding notes and formatting chords for Piano is somewhat straightforward. Doing so for a Guitar fretboard presents many difficult challenges.

    One such challenge is given a set of notes from a known music chord, write some code which will take those notes as input and iterate over the guitar fretboard to locate a reasonable set of guitar string/fret locations for each note. The string/fret locations may then be used to display a guitar 'Fret Diagram' of the given chord.

    Since this is code we would like it to be flexible, ideally taking in some additional information beyond the music chord.

    * Given a music chord find all the string/fret locations for each note
    * Utilize 'Range' information to limit the Iterator to a fretboard section
    * Rely upon a listing that maps all 88 piano midi codes to their notes/octaves
    * (this will be needed to 'tune' the six strings on a guitar)