PageRenderTime 61ms CodeModel.GetById 24ms RepoModel.GetById 0ms app.codeStats 1ms

/src/com/midisheetmusicmemo/SheetMusic.java

https://github.com/dirfox1477/midisheetmusicmemo
Java | 2080 lines | 1518 code | 238 blank | 324 comment | 378 complexity | 780a7bc64970c793f999016b48c6b2a4 MD5 | raw file

Large files files are truncated, but you can click here to view the full file

  1. /*
  2. * Copyright (c) 2007-2012 Madhav Vaidyanathan
  3. *
  4. * This program is free software; you can redistribute it and/or modify
  5. * it under the terms of the GNU General Public License version 2.
  6. *
  7. * This program is distributed in the hope that it will be useful,
  8. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  9. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  10. * GNU General Public License for more details.
  11. */
  12. package com.midisheetmusicmemo;
  13. import java.util.*;
  14. import java.io.*;
  15. import com.quadpixels.midisheetmusicmemo.TommyConfig;
  16. import com.quadpixels.midisheetmusicmemo.TommyIntroActivity;
  17. import com.quadpixels.midisheetmusicmemo.TommyView2Activity;
  18. import android.annotation.SuppressLint;
  19. import android.app.*;
  20. import android.content.*;
  21. import android.graphics.*;
  22. import android.graphics.Bitmap.Config;
  23. import android.graphics.Paint.Style;
  24. import android.os.*;
  25. import android.util.Log;
  26. import android.view.*;
  27. import android.view.animation.AnimationUtils;
  28. /** @class BoxedInt **/
  29. class BoxedInt {
  30. public int value;
  31. }
  32. /** @class SheetMusic
  33. *
  34. * The SheetMusic Control is the main class for displaying the sheet music.
  35. * The SheetMusic class has the following public methods:
  36. *
  37. * SheetMusic()
  38. * Create a new SheetMusic control from the given midi file and options.
  39. *
  40. * onDraw()
  41. * Method called to draw the SheetMuisc
  42. *
  43. * shadeNotes()
  44. * Shade all the notes played at a given pulse time.
  45. */
  46. public class SheetMusic extends SurfaceView implements SurfaceHolder.Callback, ScrollAnimationListener {
  47. public static class Vec2 {
  48. public int x, y;
  49. public Vec2() { x = 0; y = 0; }
  50. public Vec2(int i, int j) {
  51. x=i; y=j;
  52. }
  53. public void assign(Vec2 other) {
  54. x=other.x; y=other.y;
  55. }
  56. };
  57. /* Measurements used when drawing. All measurements are in pixels. */
  58. public static final int LineWidth = 1; /** The width of a line */
  59. public static final int LeftMargin = 4; /** The left margin */
  60. public static final int LineSpace = 7; /** The space between lines in the staff */
  61. public static final int StaffHeight = LineSpace*4 + LineWidth*5; /** The height between the 5 horizontal lines of the staff */
  62. public static final int NoteHeight = LineSpace + LineWidth; /** The height of a whole note */
  63. public static final int NoteWidth = 3 * LineSpace/2; /** The width of a whole note */
  64. public static final int PageWidth = 800; /** The width of each page */
  65. public static final int PageHeight = 1050; /** The height of each page (when printing) */
  66. public static final int TitleHeight = 14; /** Height of title on first page */
  67. public static final int ImmediateScroll = 1;
  68. public static final int GradualScroll = 2;
  69. public static final int DontScroll = 3;
  70. private ArrayList<Staff> staffs; /** The array of staffs to display (from top to bottom) */
  71. private KeySignature mainkey; /** The main key signature */
  72. private String filename; /** The midi filename */
  73. private int numtracks; /** The number of tracks */
  74. private int numtracks_raw; // Number of tracks in raw MIDI file.
  75. private float zoom; /** The zoom level to draw at (1.0 == 100%) */
  76. private boolean scrollVert; /** Whether to scroll vertically or horizontally */
  77. private int showNoteLetters; /** Display the note letters */
  78. private int[] NoteColors; /** The note colors to use */
  79. private int shade1; /** The color for shading */
  80. private int shade2; /** The color for shading left-hand piano */
  81. private Paint paint; /** The paint for drawing */
  82. private boolean surfaceReady; /** True if we can draw on the surface */
  83. private Bitmap bufferBitmap; /** The bitmap for drawing */
  84. private Canvas bufferCanvas; /** The canvas for drawing */
  85. private MidiPlayer player; /** For pausing the music */
  86. private int playerHeight; /** Height of the midi player */
  87. private int screenwidth; /** The screen width */
  88. private int screenheight; /** The screen height */
  89. /* fields used for scrolling */
  90. private int sheetwidth; /** The sheet music width (excluding zoom) */
  91. private int sheetheight; /** The sheet music height (excluding zoom) */
  92. private int viewwidth; /** The width of this view. */
  93. private int viewheight; /** The height of this view. */
  94. private int bufferX; /** The (left,top) of the bufferCanvas */
  95. private int bufferY;
  96. private int scrollX; /** The (left,top) of the scroll clip */
  97. private int scrollY;
  98. private ScrollAnimation scrollAnimation;
  99. // Each staff has an ArrayList
  100. // So we have an arraylist of arraylists
  101. private ArrayList<ArrayList<Bitmap> > measureBMPs = new ArrayList<ArrayList<Bitmap> >();
  102. private ArrayList<ArrayList<Integer> > measureHashes = new ArrayList<ArrayList<Integer> >();
  103. public ArrayList<ArrayList<Integer>> getMeasureHashes() {
  104. return measureHashes;
  105. }
  106. private ArrayList<Integer> measureHeights = new ArrayList<Integer>();
  107. private ArrayList<Integer> measureWidths = new ArrayList<Integer>();
  108. private ArrayList<Float> measureOverflowZooms = new ArrayList<Float>();
  109. private ArrayList<Float> measurePads = new ArrayList<Float>();
  110. private int num_notes = 0;
  111. public int getNumNotes() { return num_notes; }
  112. public String getFilename() { return filename; }
  113. public int getNumMeasures() {
  114. if(measureHashes.size() > 0)
  115. return measureHashes.get(0).size();
  116. else return 0;
  117. }
  118. public int getNumStaffs() { return measureHashes.size(); }
  119. public ArrayList<Integer> getMeasureHeights() { return measureHeights; }
  120. public ArrayList<Integer> getMeasureWidths() { return measureWidths; }
  121. private Bitmap scratch;
  122. private int scratch_width = -999, scratch_height = -999;
  123. public int render_width = 800;
  124. public boolean is_first_measure_out_of_boundingbox = true;
  125. ArrayList<ArrayList<MusicSymbol>> allsymbols, allsymbols_bk;
  126. ArrayList<ArrayList<LyricSymbol>> lyrics;
  127. MidiOptions options;
  128. @SuppressWarnings("unchecked")
  129. private void resumeBackupAllSymbols() {
  130. for(ArrayList<MusicSymbol> x : allsymbols) {
  131. for(MusicSymbol s : x) {
  132. s.setWidth(s.getMinWidth());
  133. if(s instanceof ChordSymbol) {
  134. ChordSymbol cs = (ChordSymbol)s;
  135. cs.clearBeams();
  136. }
  137. }
  138. }
  139. SymbolWidths widths = new SymbolWidths(allsymbols, lyrics);
  140. AlignSymbols(allsymbols, widths, options);
  141. }
  142. TimeSignature time_signature;
  143. // Added 20140307
  144. public boolean is_tommy_linebreak = false;
  145. public SheetMusic(Context context) {
  146. super(context);
  147. if(!(context instanceof TommyIntroActivity || context instanceof TommyView2Activity)) {
  148. SurfaceHolder holder = getHolder();
  149. holder.addCallback(this);
  150. screenwidth = 800; screenheight = 480;
  151. } else {
  152. is_tommy_linebreak = true;
  153. }
  154. bufferX = bufferY = scrollX = scrollY = 0;
  155. }
  156. // Added 20140311
  157. private int curr_playing_measure_idx = -1;
  158. private float curr_playing_measure_shade_x_begin = 0.0f,
  159. curr_playing_measure_shade_x_end = 0.0f;
  160. public int getCurrentPlayingMeasure() {
  161. return curr_playing_measure_idx;
  162. }
  163. public float getCurrentPlayingMeasureShadeXBegin() {
  164. return curr_playing_measure_shade_x_begin;
  165. }
  166. public float getCurrentPlayingMeasureShadeXEnd() {
  167. return curr_playing_measure_shade_x_end;
  168. }
  169. public int getMeasureBeginPulse(int midx) {
  170. return staffs.get(0).getMeasureBeginPulse(midx);
  171. }
  172. // Added 20140315
  173. public void getVisibleActualTrackIndices(ArrayList<Integer> actual_tidx) {
  174. actual_tidx.clear();
  175. if(numtracks_raw == 2) {
  176. if(options.tracks[0])
  177. actual_tidx.add(0);
  178. if(options.tracks[1])
  179. actual_tidx.add(1);
  180. } else {
  181. if(options.twoStaffs) {
  182. actual_tidx.add(0);
  183. actual_tidx.add(1);
  184. } else {
  185. for(int i=0; i<options.tracks.length; i++) {
  186. if(options.tracks[i]==true) {
  187. actual_tidx.add(2 + i);
  188. }
  189. }
  190. }
  191. }
  192. }
  193. public int getActualNumberOfTracks() {
  194. if(numtracks_raw== 2) return 2;
  195. else return 2+numtracks_raw;
  196. }
  197. /** Create a new SheetMusic View.
  198. * MidiFile is the parsed midi file to display.
  199. * SheetMusic Options are the menu options that were selected.
  200. *
  201. * - Apply all the Menu Options to the MidiFile tracks.
  202. * - Calculate the key signature
  203. * - For each track, create a list of MusicSymbols (notes, rests, bars, etc)
  204. * - Vertically align the music symbols in all the tracks
  205. * - Partition the music notes into horizontal staffs
  206. */
  207. public void init(MidiFile file, MidiOptions options) {
  208. numtracks_raw = file.getTracks().size();
  209. if (options == null) {
  210. options = new MidiOptions(file);
  211. }
  212. zoom = 1.0f;
  213. filename = file.getFileName();
  214. SetColors(null, options.shade1Color, options.shade2Color);
  215. paint = new Paint();
  216. paint.setTextSize(12.0f);
  217. Typeface typeface = Typeface.create(paint.getTypeface(), Typeface.NORMAL);
  218. paint.setTypeface(typeface);
  219. paint.setColor(Color.BLACK);
  220. ArrayList<MidiTrack> tracks = file.ChangeMidiNotes(options);
  221. // SetNoteSize(options.largeNoteSize);
  222. scrollVert = options.scrollVert;
  223. showNoteLetters = options.showNoteLetters;
  224. TimeSignature time = file.getTime();
  225. if (options.time != null) {
  226. time = options.time;
  227. }
  228. if (options.key == -1) {
  229. mainkey = GetKeySignature(tracks);
  230. }
  231. else {
  232. mainkey = new KeySignature(options.key);
  233. }
  234. numtracks = tracks.size();
  235. int lastStart = file.EndTime() + options.shifttime;
  236. /* Create all the music symbols (notes, rests, vertical bars, and
  237. * clef changes). The symbols variable contains a list of music
  238. * symbols for each track. The list does not include the left-side
  239. * Clef and key signature symbols. Those can only be calculated
  240. * when we create the staffs.
  241. */
  242. ArrayList<ArrayList<MusicSymbol>> allsymbols =
  243. new ArrayList<ArrayList<MusicSymbol> >(numtracks);
  244. for (int tracknum = 0; tracknum < numtracks; tracknum++) {
  245. MidiTrack track = tracks.get(tracknum);
  246. ClefMeasures clefs = new ClefMeasures(track.getNotes(), time.getMeasure());
  247. ArrayList<ChordSymbol> chords = CreateChords(track.getNotes(), mainkey, time, clefs);
  248. allsymbols.add(CreateSymbols(chords, clefs, time, lastStart));
  249. }
  250. lyrics = null;
  251. if (options.showLyrics) {
  252. lyrics = GetLyrics(tracks);
  253. }
  254. /* Vertically align the music symbols */
  255. SymbolWidths widths = new SymbolWidths(allsymbols, lyrics);
  256. AlignSymbols(allsymbols, widths, options);
  257. staffs = CreateStaffs(allsymbols, mainkey, options, time.getMeasure());
  258. Log.v("SheetMusic", staffs.size() + " staffs");
  259. if(!is_tommy_linebreak) {
  260. CreateAllBeamedChords(allsymbols, time);
  261. } else {
  262. this.allsymbols = allsymbols;
  263. this.time_signature = time;
  264. }
  265. if (lyrics != null) {
  266. AddLyricsToStaffs(staffs, lyrics);
  267. }
  268. /* After making chord pairs, the stem directions can change,
  269. * which affects the staff height. Re-calculate the staff height.
  270. */
  271. for (Staff staff : staffs) {
  272. staff.CalculateHeight();
  273. }
  274. zoom = 1.0f;
  275. scrollAnimation = new ScrollAnimation(this, scrollVert);
  276. this.options = options;
  277. }
  278. /** Calculate the size of the sheet music width and height
  279. * (without zoom scaling to fit the screen). Store the result in
  280. * sheetwidth and sheetheight.
  281. */
  282. private void calculateSize() {
  283. sheetwidth = 0;
  284. sheetheight = 0;
  285. for (Staff staff : staffs) {
  286. sheetwidth = Math.max(sheetwidth, staff.getWidth());
  287. sheetheight += (staff.getHeight());
  288. }
  289. sheetwidth += 2;
  290. sheetheight += LeftMargin;
  291. }
  292. /* Adjust the zoom level so that the sheet music page (PageWidth)
  293. * fits within the width. If the heightspec is 0, return the screenheight.
  294. * Else, use the given view width/height.
  295. */
  296. @Override
  297. protected void onMeasure(int widthspec, int heightspec) {
  298. // First, calculate the zoom level
  299. int specwidth = MeasureSpec.getSize(widthspec);
  300. int specheight = MeasureSpec.getSize(heightspec);
  301. if (specwidth == 0 && specheight == 0) {
  302. setMeasuredDimension(screenwidth, screenheight);
  303. }
  304. else if (specwidth == 0) {
  305. setMeasuredDimension(screenwidth, specheight);
  306. }
  307. else if (specheight == 0) {
  308. setMeasuredDimension(specwidth, screenheight);
  309. }
  310. else {
  311. setMeasuredDimension(specwidth, specheight);
  312. }
  313. }
  314. /** If this is the first size change, calculate the zoom level,
  315. * and create the bufferCanvas. Otherwise, do nothing.
  316. */
  317. @Override
  318. protected void
  319. onSizeChanged(int newwidth, int newheight, int oldwidth, int oldheight) {
  320. viewwidth = newwidth;
  321. viewheight = newheight;
  322. if (bufferCanvas != null) {
  323. callOnDraw();
  324. return;
  325. }
  326. calculateSize();
  327. if (scrollVert) {
  328. zoom = (float)((newwidth - 2) * 1.0 / PageWidth);
  329. }
  330. else {
  331. zoom = (float)( (newheight + playerHeight) * 1.0 / sheetheight);
  332. if (zoom < 0.9)
  333. zoom = 0.9f;
  334. if (zoom > 1.1)
  335. zoom = 1.1f;
  336. }
  337. if (bufferCanvas == null) {
  338. createBufferCanvas();
  339. }
  340. callOnDraw();
  341. }
  342. /** Get the best key signature given the midi notes in all the tracks. */
  343. private KeySignature GetKeySignature(ArrayList<MidiTrack> tracks) {
  344. ListInt notenums = new ListInt();
  345. for (MidiTrack track : tracks) {
  346. for (MidiNote note : track.getNotes()) {
  347. notenums.add(note.getNumber());
  348. }
  349. }
  350. return KeySignature.Guess(notenums);
  351. }
  352. /** Create the chord symbols for a single track.
  353. * @param midinotes The Midinotes in the track.
  354. * @param key The Key Signature, for determining sharps/flats.
  355. * @param time The Time Signature, for determining the measures.
  356. * @param clefs The clefs to use for each measure.
  357. * @ret An array of ChordSymbols
  358. */
  359. private
  360. ArrayList<ChordSymbol> CreateChords(ArrayList<MidiNote> midinotes,
  361. KeySignature key,
  362. TimeSignature time,
  363. ClefMeasures clefs) {
  364. int i = 0;
  365. ArrayList<ChordSymbol> chords = new ArrayList<ChordSymbol>();
  366. ArrayList<MidiNote> notegroup = new ArrayList<MidiNote>(12);
  367. int len = midinotes.size();
  368. while (i < len) {
  369. int starttime = midinotes.get(i).getStartTime();
  370. Clef clef = clefs.GetClef(starttime);
  371. /* Group all the midi notes with the same start time
  372. * into the notes list.
  373. */
  374. notegroup.clear();
  375. notegroup.add(midinotes.get(i));
  376. i++;
  377. while (i < len && midinotes.get(i).getStartTime() == starttime) {
  378. notegroup.add(midinotes.get(i));
  379. i++;
  380. }
  381. /* Create a single chord from the group of midi notes with
  382. * the same start time.
  383. */
  384. ChordSymbol chord = new ChordSymbol(notegroup, key, time, clef, this);
  385. chords.add(chord);
  386. }
  387. return chords;
  388. }
  389. /** Given the chord symbols for a track, create a new symbol list
  390. * that contains the chord symbols, vertical bars, rests, and
  391. * clef changes.
  392. * Return a list of symbols (ChordSymbol, BarSymbol, RestSymbol, ClefSymbol)
  393. */
  394. private ArrayList<MusicSymbol>
  395. CreateSymbols(ArrayList<ChordSymbol> chords, ClefMeasures clefs,
  396. TimeSignature time, int lastStart) {
  397. ArrayList<MusicSymbol> symbols = new ArrayList<MusicSymbol>();
  398. symbols = AddBars(chords, time, lastStart);
  399. symbols = AddRests(symbols, time);
  400. symbols = AddClefChanges(symbols, clefs, time);
  401. return symbols;
  402. }
  403. /** Add in the vertical bars delimiting measures.
  404. * Also, add the time signature symbols.
  405. */
  406. private ArrayList<MusicSymbol>
  407. AddBars(ArrayList<ChordSymbol> chords, TimeSignature time, int lastStart) {
  408. ArrayList<MusicSymbol> symbols = new ArrayList<MusicSymbol>();
  409. TimeSigSymbol timesig = new TimeSigSymbol(time.getNumerator(), time.getDenominator());
  410. symbols.add(timesig);
  411. /* The starttime of the beginning of the measure */
  412. int measuretime = 0;
  413. int i = 0;
  414. while (i < chords.size()) {
  415. if (measuretime <= chords.get(i).getStartTime()) {
  416. symbols.add(new BarSymbol(measuretime) );
  417. measuretime += time.getMeasure();
  418. }
  419. else {
  420. symbols.add(chords.get(i));
  421. i++;
  422. }
  423. }
  424. /* Keep adding bars until the last StartTime (the end of the song) */
  425. while (measuretime < lastStart) {
  426. symbols.add(new BarSymbol(measuretime) );
  427. measuretime += time.getMeasure();
  428. }
  429. /* Add the final vertical bar to the last measure */
  430. symbols.add(new BarSymbol(measuretime) );
  431. return symbols;
  432. }
  433. /** Add rest symbols between notes. All times below are
  434. * measured in pulses.
  435. */
  436. private
  437. ArrayList<MusicSymbol> AddRests(ArrayList<MusicSymbol> symbols, TimeSignature time) {
  438. int prevtime = 0;
  439. ArrayList<MusicSymbol> result = new ArrayList<MusicSymbol>( symbols.size() );
  440. for (MusicSymbol symbol : symbols) {
  441. int starttime = symbol.getStartTime();
  442. RestSymbol[] rests = GetRests(time, prevtime, starttime);
  443. if (rests != null) {
  444. for (RestSymbol r : rests) {
  445. result.add(r);
  446. }
  447. }
  448. result.add(symbol);
  449. /* Set prevtime to the end time of the last note/symbol. */
  450. if (symbol instanceof ChordSymbol) {
  451. ChordSymbol chord = (ChordSymbol)symbol;
  452. prevtime = Math.max( chord.getEndTime(), prevtime );
  453. }
  454. else {
  455. prevtime = Math.max(starttime, prevtime);
  456. }
  457. }
  458. return result;
  459. }
  460. /** Return the rest symbols needed to fill the time interval between
  461. * start and end. If no rests are needed, return nil.
  462. */
  463. private
  464. RestSymbol[] GetRests(TimeSignature time, int start, int end) {
  465. RestSymbol[] result;
  466. RestSymbol r1, r2;
  467. if (end - start < 0)
  468. return null;
  469. NoteDuration dur = time.GetNoteDuration(end - start);
  470. switch (dur) {
  471. case Whole:
  472. case Half:
  473. case Quarter:
  474. case Eighth:
  475. r1 = new RestSymbol(start, dur);
  476. result = new RestSymbol[]{ r1 };
  477. return result;
  478. case DottedHalf:
  479. r1 = new RestSymbol(start, NoteDuration.Half);
  480. r2 = new RestSymbol(start + time.getQuarter()*2,
  481. NoteDuration.Quarter);
  482. result = new RestSymbol[]{ r1, r2 };
  483. return result;
  484. case DottedQuarter:
  485. r1 = new RestSymbol(start, NoteDuration.Quarter);
  486. r2 = new RestSymbol(start + time.getQuarter(),
  487. NoteDuration.Eighth);
  488. result = new RestSymbol[]{ r1, r2 };
  489. return result;
  490. case DottedEighth:
  491. r1 = new RestSymbol(start, NoteDuration.Eighth);
  492. r2 = new RestSymbol(start + time.getQuarter()/2,
  493. NoteDuration.Sixteenth);
  494. result = new RestSymbol[]{ r1, r2 };
  495. return result;
  496. default:
  497. return null;
  498. }
  499. }
  500. /** The current clef is always shown at the beginning of the staff, on
  501. * the left side. However, the clef can also change from measure to
  502. * measure. When it does, a Clef symbol must be shown to indicate the
  503. * change in clef. This function adds these Clef change symbols.
  504. * This function does not add the main Clef Symbol that begins each
  505. * staff. That is done in the Staff() contructor.
  506. */
  507. private
  508. ArrayList<MusicSymbol> AddClefChanges(ArrayList<MusicSymbol> symbols,
  509. ClefMeasures clefs,
  510. TimeSignature time) {
  511. ArrayList<MusicSymbol> result = new ArrayList<MusicSymbol>( symbols.size() );
  512. Clef prevclef = clefs.GetClef(0);
  513. for (MusicSymbol symbol : symbols) {
  514. /* A BarSymbol indicates a new measure */
  515. if (symbol instanceof BarSymbol) {
  516. Clef clef = clefs.GetClef(symbol.getStartTime());
  517. if (clef != prevclef) {
  518. result.add(new ClefSymbol(clef, symbol.getStartTime()-1, true));
  519. }
  520. prevclef = clef;
  521. }
  522. result.add(symbol);
  523. }
  524. return result;
  525. }
  526. /** Notes with the same start times in different staffs should be
  527. * vertically aligned. The SymbolWidths class is used to help
  528. * vertically align symbols.
  529. *
  530. * First, each track should have a symbol for every starttime that
  531. * appears in the Midi File. If a track doesn't have a symbol for a
  532. * particular starttime, then add a "blank" symbol for that time.
  533. *
  534. * Next, make sure the symbols for each start time all have the same
  535. * width, across all tracks. The SymbolWidths class stores
  536. * - The symbol width for each starttime, for each track
  537. * - The maximum symbol width for a given starttime, across all tracks.
  538. *
  539. * The method SymbolWidths.GetExtraWidth() returns the extra width
  540. * needed for a track to match the maximum symbol width for a given
  541. * starttime.
  542. */
  543. private
  544. void AlignSymbols(ArrayList<ArrayList<MusicSymbol>> allsymbols, SymbolWidths widths, MidiOptions options) {
  545. // If we show measure numbers, increase bar symbol width
  546. if (options.showMeasures) {
  547. for (int track = 0; track < allsymbols.size(); track++) {
  548. ArrayList<MusicSymbol> symbols = allsymbols.get(track);
  549. for (MusicSymbol sym : symbols) {
  550. if (sym instanceof BarSymbol) {
  551. sym.setWidth( sym.getWidth() + NoteWidth);
  552. }
  553. }
  554. }
  555. }
  556. for (int track = 0; track < allsymbols.size(); track++) {
  557. ArrayList<MusicSymbol> symbols = allsymbols.get(track);
  558. ArrayList<MusicSymbol> result = new ArrayList<MusicSymbol>();
  559. int i = 0;
  560. /* If a track doesn't have a symbol for a starttime,
  561. * add a blank symbol.
  562. */
  563. for (int start : widths.getStartTimes()) {
  564. /* BarSymbols are not included in the SymbolWidths calculations */
  565. while (i < symbols.size() && (symbols.get(i) instanceof BarSymbol) &&
  566. symbols.get(i).getStartTime() <= start) {
  567. result.add(symbols.get(i));
  568. i++;
  569. }
  570. if (i < symbols.size() && symbols.get(i).getStartTime() == start) {
  571. while (i < symbols.size() &&
  572. symbols.get(i).getStartTime() == start) {
  573. result.add(symbols.get(i));
  574. i++;
  575. }
  576. }
  577. else {
  578. result.add(new BlankSymbol(start, 0));
  579. }
  580. }
  581. /* For each starttime, increase the symbol width by
  582. * SymbolWidths.GetExtraWidth().
  583. */
  584. i = 0;
  585. while (i < result.size()) {
  586. if (result.get(i) instanceof BarSymbol) {
  587. i++;
  588. continue;
  589. }
  590. int start = result.get(i).getStartTime();
  591. int extra = widths.GetExtraWidth(track, start);
  592. int newwidth = result.get(i).getWidth() + extra;
  593. result.get(i).setWidth(newwidth);
  594. /* Skip all remaining symbols with the same starttime. */
  595. while (i < result.size() && result.get(i).getStartTime() == start) {
  596. i++;
  597. }
  598. }
  599. allsymbols.set(track, result);
  600. }
  601. }
  602. /** Find 2, 3, 4, or 6 chord symbols that occur consecutively (without any
  603. * rests or bars in between). There can be BlankSymbols in between.
  604. *
  605. * The startIndex is the index in the symbols to start looking from.
  606. *
  607. * Store the indexes of the consecutive chords in chordIndexes.
  608. * Store the horizontal distance (pixels) between the first and last chord.
  609. * If we failed to find consecutive chords, return false.
  610. */
  611. private static boolean
  612. FindConsecutiveChords(ArrayList<MusicSymbol> symbols, TimeSignature time,
  613. int startIndex, int[] chordIndexes,
  614. BoxedInt horizDistance) {
  615. int i = startIndex;
  616. int numChords = chordIndexes.length;
  617. while (true) {
  618. horizDistance.value = 0;
  619. /* Find the starting chord */
  620. while (i < symbols.size() - numChords) {
  621. if (symbols.get(i) instanceof ChordSymbol) {
  622. ChordSymbol c = (ChordSymbol) symbols.get(i);
  623. if (c.getStem() != null) {
  624. break;
  625. }
  626. }
  627. i++;
  628. }
  629. if (i >= symbols.size() - numChords) {
  630. chordIndexes[0] = -1;
  631. return false;
  632. }
  633. chordIndexes[0] = i;
  634. boolean foundChords = true;
  635. for (int chordIndex = 1; chordIndex < numChords; chordIndex++) {
  636. i++;
  637. int remaining = numChords - 1 - chordIndex;
  638. while ((i < symbols.size() - remaining) &&
  639. (symbols.get(i) instanceof BlankSymbol)) {
  640. horizDistance.value += symbols.get(i).getWidth();
  641. i++;
  642. }
  643. if (i >= symbols.size() - remaining) {
  644. return false;
  645. }
  646. if (!(symbols.get(i) instanceof ChordSymbol)) {
  647. foundChords = false;
  648. break;
  649. }
  650. chordIndexes[chordIndex] = i;
  651. horizDistance.value += symbols.get(i).getWidth();
  652. }
  653. if (foundChords) {
  654. return true;
  655. }
  656. /* Else, start searching again from index i */
  657. }
  658. }
  659. /** Connect chords of the same duration with a horizontal beam.
  660. * numChords is the number of chords per beam (2, 3, 4, or 6).
  661. * if startBeat is true, the first chord must start on a quarter note beat.
  662. */
  663. private static void
  664. CreateBeamedChords(ArrayList<ArrayList<MusicSymbol>> allsymbols, TimeSignature time,
  665. int numChords, boolean startBeat) {
  666. int[] chordIndexes = new int[numChords];
  667. ChordSymbol[] chords = new ChordSymbol[numChords];
  668. for (ArrayList<MusicSymbol> symbols : allsymbols) {
  669. int startIndex = 0;
  670. while (true) {
  671. BoxedInt horizDistance = new BoxedInt();
  672. horizDistance.value = 0;
  673. boolean found = FindConsecutiveChords(symbols, time,
  674. startIndex,
  675. chordIndexes,
  676. horizDistance);
  677. if (!found) {
  678. break;
  679. }
  680. for (int i = 0; i < numChords; i++) {
  681. chords[i] = (ChordSymbol)symbols.get( chordIndexes[i] );
  682. }
  683. if (ChordSymbol.CanCreateBeam(chords, time, startBeat)) {
  684. ChordSymbol.CreateBeam(chords, horizDistance.value);
  685. startIndex = chordIndexes[numChords-1] + 1;
  686. }
  687. else {
  688. startIndex = chordIndexes[0] + 1;
  689. }
  690. /* What is the value of startIndex here?
  691. * If we created a beam, we start after the last chord.
  692. * If we failed to create a beam, we start after the first chord.
  693. */
  694. }
  695. }
  696. }
  697. /** Connect chords of the same duration with a horizontal beam.
  698. *
  699. * We create beams in the following order:
  700. * - 6 connected 8th note chords, in 3/4, 6/8, or 6/4 time
  701. * - Triplets that start on quarter note beats
  702. * - 3 connected chords that start on quarter note beats (12/8 time only)
  703. * - 4 connected chords that start on quarter note beats (4/4 or 2/4 time only)
  704. * - 2 connected chords that start on quarter note beats
  705. * - 2 connected chords that start on any beat
  706. */
  707. private static void
  708. CreateAllBeamedChords(ArrayList<ArrayList<MusicSymbol>> allsymbols, TimeSignature time) {
  709. if ((time.getNumerator() == 3 && time.getDenominator() == 4) ||
  710. (time.getNumerator() == 6 && time.getDenominator() == 8) ||
  711. (time.getNumerator() == 6 && time.getDenominator() == 4) ) {
  712. CreateBeamedChords(allsymbols, time, 6, true);
  713. }
  714. CreateBeamedChords(allsymbols, time, 3, true);
  715. CreateBeamedChords(allsymbols, time, 4, true);
  716. CreateBeamedChords(allsymbols, time, 2, true);
  717. CreateBeamedChords(allsymbols, time, 2, false);
  718. }
  719. /** Get the width (in pixels) needed to display the key signature */
  720. public static int
  721. KeySignatureWidth(KeySignature key) {
  722. ClefSymbol clefsym = new ClefSymbol(Clef.Treble, 0, false);
  723. int result = clefsym.getMinWidth();
  724. AccidSymbol[] keys = key.GetSymbols(Clef.Treble);
  725. for (AccidSymbol symbol : keys) {
  726. result += symbol.getMinWidth();
  727. }
  728. return result + SheetMusic.LeftMargin + 5;
  729. }
  730. /** Given MusicSymbols for a track, create the staffs for that track.
  731. * Each Staff has a maxmimum width of PageWidth (800 pixels).
  732. * Also, measures should not span multiple Staffs.
  733. */
  734. private ArrayList<Staff>
  735. CreateStaffsForTrack(ArrayList<MusicSymbol> symbols, int measurelen,
  736. KeySignature key, MidiOptions options,
  737. int track, int totaltracks) {
  738. int keysigWidth = KeySignatureWidth(key);
  739. int startindex = 0;
  740. ArrayList<Staff> thestaffs = new ArrayList<Staff>(symbols.size() / 50);
  741. while (startindex < symbols.size()) {
  742. /* startindex is the index of the first symbol in the staff.
  743. * endindex is the index of the last symbol in the staff.
  744. */
  745. int endindex = startindex;
  746. int width = keysigWidth;
  747. int maxwidth;
  748. /* If we're scrolling vertically, the maximum width is PageWidth. */
  749. if (scrollVert) {
  750. maxwidth = SheetMusic.PageWidth;
  751. }
  752. else {
  753. maxwidth = 2000000;
  754. }
  755. while (endindex < symbols.size() &&
  756. width + symbols.get(endindex).getWidth() < maxwidth) {
  757. width += symbols.get(endindex).getWidth();
  758. endindex++;
  759. }
  760. endindex--;
  761. /* There's 3 possibilities at this point:
  762. * 1. We have all the symbols in the track.
  763. * The endindex stays the same.
  764. *
  765. * 2. We have symbols for less than one measure.
  766. * The endindex stays the same.
  767. *
  768. * 3. We have symbols for 1 or more measures.
  769. * Since measures cannot span multiple staffs, we must
  770. * make sure endindex does not occur in the middle of a
  771. * measure. We count backwards until we come to the end
  772. * of a measure.
  773. */
  774. if (endindex == symbols.size() - 1) {
  775. /* endindex stays the same */
  776. }
  777. else if (symbols.get(startindex).getStartTime() / measurelen ==
  778. symbols.get(endindex).getStartTime() / measurelen) {
  779. /* endindex stays the same */
  780. }
  781. else {
  782. int endmeasure = symbols.get(endindex+1).getStartTime()/measurelen;
  783. while (symbols.get(endindex).getStartTime() / measurelen ==
  784. endmeasure) {
  785. endindex--;
  786. }
  787. }
  788. if (scrollVert) {
  789. width = SheetMusic.PageWidth;
  790. }
  791. // int range = endindex + 1 - startindex;
  792. ArrayList<MusicSymbol> staffSymbols = new ArrayList<MusicSymbol>();
  793. for (int i = startindex; i <= endindex; i++) {
  794. staffSymbols.add(symbols.get(i));
  795. }
  796. Staff staff = new Staff(staffSymbols, key, options, track, totaltracks);
  797. thestaffs.add(staff);
  798. startindex = endindex + 1;
  799. }
  800. return thestaffs;
  801. }
  802. /** Given all the MusicSymbols for every track, create the staffs
  803. * for the sheet music. There are two parts to this:
  804. *
  805. * - Get the list of staffs for each track.
  806. * The staffs will be stored in trackstaffs as:
  807. *
  808. * trackstaffs[0] = { Staff0, Staff1, Staff2, ... } for track 0
  809. * trackstaffs[1] = { Staff0, Staff1, Staff2, ... } for track 1
  810. * trackstaffs[2] = { Staff0, Staff1, Staff2, ... } for track 2
  811. *
  812. * - Store the Staffs in the staffs list, but interleave the
  813. * tracks as follows:
  814. *
  815. * staffs = { Staff0 for track 0, Staff0 for track1, Staff0 for track2,
  816. * Staff1 for track 0, Staff1 for track1, Staff1 for track2,
  817. * Staff2 for track 0, Staff2 for track1, Staff2 for track2,
  818. * ... }
  819. */
  820. private ArrayList<Staff>
  821. CreateStaffs(ArrayList<ArrayList<MusicSymbol>> allsymbols, KeySignature key,
  822. MidiOptions options, int measurelen) {
  823. ArrayList<ArrayList<Staff>> trackstaffs =
  824. new ArrayList<ArrayList<Staff>>( allsymbols.size() );
  825. int totaltracks = allsymbols.size();
  826. for (int track = 0; track < totaltracks; track++) {
  827. ArrayList<MusicSymbol> symbols = allsymbols.get( track );
  828. trackstaffs.add(CreateStaffsForTrack(symbols, measurelen, key,
  829. options, track, totaltracks));
  830. }
  831. /* Update the EndTime of each Staff. EndTime is used for playback */
  832. for (ArrayList<Staff> list : trackstaffs) {
  833. for (int i = 0; i < list.size()-1; i++) {
  834. list.get(i).setEndTime( list.get(i+1).getStartTime() );
  835. }
  836. }
  837. /* Interleave the staffs of each track into the result array. */
  838. int maxstaffs = 0;
  839. for (int i = 0; i < trackstaffs.size(); i++) {
  840. if (maxstaffs < trackstaffs.get(i).size()) {
  841. maxstaffs = trackstaffs.get(i).size();
  842. }
  843. }
  844. ArrayList<Staff> result = new ArrayList<Staff>(maxstaffs * trackstaffs.size());
  845. for (int i = 0; i < maxstaffs; i++) {
  846. for (ArrayList<Staff> list : trackstaffs) {
  847. if (i < list.size()) {
  848. result.add(list.get(i));
  849. }
  850. }
  851. }
  852. return result;
  853. }
  854. /** Change the note colors for the sheet music, and redraw.
  855. * This is not currently used.
  856. */
  857. public void SetColors(int[] newcolors, int newshade1, int newshade2) {
  858. if (NoteColors == null) {
  859. NoteColors = new int[12];
  860. for (int i = 0; i < 12; i++) {
  861. NoteColors[i] = Color.BLACK;
  862. }
  863. }
  864. if (newcolors != null) {
  865. for (int i = 0; i < 12; i++) {
  866. NoteColors[i] = newcolors[i];
  867. }
  868. }
  869. shade1 = newshade1;
  870. shade2 = newshade2;
  871. }
  872. /** Get the color for a given note number. Not currently used. */
  873. public int NoteColor(int number) {
  874. return NoteColors[ NoteScale.FromNumber(number) ];
  875. }
  876. /** Get the shade color */
  877. public int getShade1() { return shade1; }
  878. /** Get the shade2 color */
  879. public int getShade2() { return shade2; }
  880. /** Get whether to show note letters or not */
  881. public int getShowNoteLetters() { return showNoteLetters; }
  882. /** Get the main key signature */
  883. public KeySignature getMainKey() { return mainkey; }
  884. /** Get the lyrics for each track */
  885. private static ArrayList<ArrayList<LyricSymbol>>
  886. GetLyrics(ArrayList<MidiTrack> tracks) {
  887. boolean hasLyrics = false;
  888. ArrayList<ArrayList<LyricSymbol>> result = new ArrayList<ArrayList<LyricSymbol>>();
  889. for (int tracknum = 0; tracknum < tracks.size(); tracknum++) {
  890. ArrayList<LyricSymbol> lyrics = new ArrayList<LyricSymbol>();
  891. result.add(lyrics);
  892. MidiTrack track = tracks.get(tracknum);
  893. if (track.getLyrics() == null) {
  894. continue;
  895. }
  896. hasLyrics = true;
  897. for (MidiEvent ev : track.getLyrics()) {
  898. try {
  899. String text = new String(ev.Value, 0, ev.Value.length, "UTF-8");
  900. LyricSymbol sym = new LyricSymbol(ev.StartTime, text);
  901. lyrics.add(sym);
  902. }
  903. catch (UnsupportedEncodingException e) {}
  904. }
  905. }
  906. if (!hasLyrics) {
  907. return null;
  908. }
  909. else {
  910. return result;
  911. }
  912. }
  913. /** Add the lyric symbols to the corresponding staffs */
  914. static void
  915. AddLyricsToStaffs(ArrayList<Staff> staffs, ArrayList<ArrayList<LyricSymbol>> tracklyrics) {
  916. for (Staff staff : staffs) {
  917. ArrayList<LyricSymbol> lyrics = tracklyrics.get(staff.getTrack());
  918. staff.AddLyrics(lyrics);
  919. }
  920. }
  921. /** Create a bitmap/canvas to use for double-buffered drawing.
  922. * This is needed for shading the notes quickly.
  923. * Instead of redrawing the entire sheet music on every shade call,
  924. * we draw the sheet music to this bitmap canvas. On subsequent
  925. * calls to ShadeNotes(), we only need to draw the delta (the
  926. * new notes to shade/unshade) onto the bitmap, and then draw the bitmap.
  927. *
  928. * We include the MidiPlayer height (since we hide the MidiPlayer
  929. * once the music starts playing). Also, we make the bitmap twice as
  930. * large as the scroll viewable area, so that we don't need to
  931. * refresh the bufferCanvas on every scroll change.
  932. */
  933. void createBufferCanvas() {
  934. if (bufferBitmap != null) {
  935. bufferCanvas = null;
  936. bufferBitmap.recycle();
  937. bufferBitmap = null;
  938. }
  939. if (scrollVert) {
  940. bufferBitmap = Bitmap.createBitmap(viewwidth,
  941. (viewheight + playerHeight) * 2,
  942. Bitmap.Config.ARGB_8888);
  943. }
  944. else {
  945. bufferBitmap = Bitmap.createBitmap(viewwidth * 2,
  946. (viewheight + playerHeight) * 2,
  947. Bitmap.Config.ARGB_8888);
  948. }
  949. bufferCanvas = new Canvas(bufferBitmap);
  950. drawToBuffer(scrollX, scrollY);
  951. }
  952. /** Obtain the drawing canvas and call onDraw() */
  953. @SuppressLint("WrongCall")
  954. public void callOnDraw() {
  955. if (!surfaceReady) {
  956. return;
  957. }
  958. SurfaceHolder holder = getHolder();
  959. Canvas canvas = holder.lockCanvas();
  960. if (canvas == null) {
  961. return;
  962. }
  963. onDraw(canvas);
  964. holder.unlockCanvasAndPost(canvas);
  965. }
  966. /** Draw the SheetMusic. */
  967. @Override
  968. protected void onDraw(Canvas canvas) {
  969. if (bufferBitmap == null) {
  970. createBufferCanvas();
  971. }
  972. if (!isScrollPositionInBuffer()) {
  973. drawToBuffer(scrollX, scrollY);
  974. }
  975. // We want (scrollX - bufferX, scrollY - bufferY)
  976. // to be (0,0) on the canvas
  977. canvas.translate(-(scrollX - bufferX), -(scrollY - bufferY));
  978. canvas.drawBitmap(bufferBitmap, 0, 0, paint);
  979. canvas.translate(scrollX - bufferX, scrollY - bufferY);
  980. }
  981. /** Return true if the scrollX/scrollY is in the bufferBitmap */
  982. private boolean isScrollPositionInBuffer() {
  983. if ((scrollY < bufferY) ||
  984. (scrollX < bufferX) ||
  985. (scrollY > bufferY + bufferBitmap.getHeight()/3) ||
  986. (scrollX > bufferX + bufferBitmap.getWidth()/3) ) {
  987. return false;
  988. }
  989. else {
  990. return true;
  991. }
  992. }
  993. /** Draw the SheetMusic to the bufferCanvas, with the
  994. * given (left,top) corner.
  995. *
  996. * Scale the graphics by the current zoom factor.
  997. * Only draw Staffs which lie inside the buffer area.
  998. */
  999. private void drawToBuffer(int left, int top) {
  1000. if (staffs == null) {
  1001. return;
  1002. }
  1003. bufferX =left;
  1004. bufferY = top;
  1005. bufferCanvas.translate(-bufferX, -bufferY);
  1006. Rect clip = new Rect(bufferX, bufferY,
  1007. bufferX + bufferBitmap.getWidth(),
  1008. bufferY + bufferBitmap.getHeight());
  1009. // Scale both the canvas and the clip by the zoom factor
  1010. clip.left = (int)(clip.left / zoom);
  1011. clip.top = (int)(clip.top / zoom);
  1012. clip.right = (int)(clip.right / zoom);
  1013. clip.bottom = (int)(clip.bottom / zoom);
  1014. bufferCanvas.scale(zoom, zoom);
  1015. // Draw a white background
  1016. paint.setAntiAlias(true);
  1017. paint.setStyle(Paint.Style.FILL);
  1018. paint.setColor(Color.WHITE);
  1019. bufferCanvas.drawRect(clip.left, clip.top, clip.right, clip.bottom, paint);
  1020. paint.setStyle(Paint.Style.STROKE);
  1021. paint.setColor(Color.BLACK);
  1022. // Draw the staffs in the clip area
  1023. int ypos = 0;
  1024. for (Staff staff : staffs) {
  1025. if ((ypos + staff.getHeight() < clip.top) || (ypos > clip.bottom)) {
  1026. /* Staff is not in the clip, don't need to draw it */
  1027. }
  1028. else {
  1029. bufferCanvas.translate(0, ypos);
  1030. staff.Draw(bufferCanvas, clip, paint);
  1031. bufferCanvas.translate(0, -ypos);
  1032. }
  1033. ypos += staff.getHeight();
  1034. }
  1035. bufferCanvas.scale(1.0f/zoom, 1.0f/zoom);
  1036. bufferCanvas.translate(bufferX, bufferY);
  1037. }
  1038. /** Write the MIDI filename at the top of the page */
  1039. private void DrawTitle(Canvas canvas) {
  1040. int leftmargin = 20;
  1041. int topmargin = 20;
  1042. String title = filename;
  1043. title = title.replace(".mid", "").replace("_", " ");
  1044. canvas.translate(leftmargin, topmargin);
  1045. canvas.drawText(title, 0, 0, paint);
  1046. canvas.translate(-leftmargin, -topmargin);
  1047. }
  1048. /**
  1049. * Return the number of pages needed to print this sheet music.
  1050. * A staff should fit within a single page, not be split across two pages.
  1051. * If the sheet music has exactly 2 tracks, then two staffs should
  1052. * fit within a single page, and not be split across two pages.
  1053. */
  1054. public int GetTotalPages() {
  1055. int num = 1;
  1056. int currheight = TitleHeight;
  1057. if (numtracks == 2 && (staffs.size() % 2) == 0) {
  1058. for (int i = 0; i < staffs.size(); i += 2) {
  1059. int heights = staffs.get(i).getHeight() + staffs.get(i+1).getHeight();
  1060. if (currheight + heights > PageHeight) {
  1061. num++;
  1062. currheight = heights;
  1063. }
  1064. else {
  1065. currheight += heights;
  1066. }
  1067. }
  1068. }
  1069. else {
  1070. for (Staff staff : staffs) {
  1071. if (currheight + staff.getHeight() > PageHeight) {
  1072. num++;
  1073. currheight = staff.getHeight();
  1074. }
  1075. else {
  1076. currheight += staff.getHeight();
  1077. }
  1078. }
  1079. }
  1080. return num;
  1081. }
  1082. /** Draw the given page of the sheet music.
  1083. * Page numbers start from 1.
  1084. * A staff should fit within a single page, not be split across two pages.
  1085. * If the sheet music has exactly 2 tracks, then two staffs should
  1086. * fit within a single page, and not be split across two pages.
  1087. */
  1088. public void DrawPage(Canvas canvas, int pagenumber)
  1089. {
  1090. int leftmargin = 20;
  1091. int topmargin = 20;
  1092. //int rightmargin = 20;
  1093. //int bottommargin = 20;
  1094. //float scale = 1.0f;
  1095. Rect clip = new Rect(0, 0, PageWidth + 40, PageHeight + 40);
  1096. paint.setAntiAlias(true);
  1097. paint.setStyle(Paint.Style.FILL);
  1098. paint.setColor(Color.WHITE);
  1099. canvas.drawRect(clip.left, clip.top, clip.right, clip.bottom, paint);
  1100. paint.setStyle(Paint.Style.STROKE);
  1101. paint.setColor(Color.BLACK);
  1102. int ypos = TitleHeight;
  1103. int pagenum = 1;
  1104. int staffnum = 0;
  1105. if (numtracks == 2 && (staffs.size() % 2) == 0) {
  1106. /* Skip the staffs until we reach the given page number */
  1107. while (staffnum + 1 < staffs.size() && pagenum < pagenumber) {
  1108. int heights = staffs.get(staffnum).getHeight() +
  1109. staffs.get(staffnum+1).getHeight();
  1110. if (ypos + heights >= PageHeight) {
  1111. pagenum++;
  1112. ypos = 0;
  1113. }
  1114. else {
  1115. ypos += heights;
  1116. staffnum += 2;
  1117. }
  1118. }
  1119. /* Print the staffs until the height reaches PageHeight */
  1120. if (pagenum == 1) {
  1121. DrawTitle(canvas);
  1122. ypos = TitleHeight;
  1123. }
  1124. else {
  1125. ypos = 0;
  1126. }
  1127. for (; staffnum + 1 < staffs.size(); staffnum += 2) {
  1128. int heights = staffs.get(staffnum).getHeight() +
  1129. staffs.get(staffnum+1).getHeight();
  1130. if (ypos + heights >= PageHeight)
  1131. break;
  1132. canvas.translate(leftmargin, topmargin + ypos);
  1133. staffs.get(staffnum).Draw(canvas, clip, paint);
  1134. canvas.translate(-leftmargin, -(topmargin + ypos));
  1135. ypos += staffs.get(staffnum).getHeight();
  1136. canvas.translate(leftmargin, topmargin + ypos);
  1137. staffs.get(staffnum + 1).Draw(canvas, clip, paint);
  1138. canvas.translate(-leftmargin, -(topmargin + ypos));
  1139. ypos += staffs.get(staffnum + 1).getHeight();
  1140. }
  1141. }
  1142. else {
  1143. /* Skip the staffs until we reach the given page number */
  1144. while (staffnum < staffs.size() && pagenum < pagenumber) {
  1145. if (ypos + staffs.get(staffnum).getHeight() >= PageHeight) {
  1146. pagenum++;
  1147. ypos = 0;
  1148. }
  1149. else {
  1150. ypos += staffs.get(staffnum).getHeight();
  1151. staffnum++;
  1152. }
  1153. }
  1154. /* Print the staffs until the height reaches viewPageHeight */
  1155. if (pagenum == 1) {
  1156. DrawTitle(canvas);
  1157. ypos = TitleHeight;
  1158. }
  1159. else {
  1160. ypos = 0;
  1161. }
  1162. for (; staffnum < staffs.size(); staffnum++) {
  1163. if (ypos + staffs.get(staffnum).getHeight() >= PageHeight)
  1164. break;
  1165. canvas.translate(leftmargin, topmargin + ypos);
  1166. staffs.get(staffnum).Draw(canvas, clip, paint);
  1167. canvas.translate(-leftmargin, -(topmargin + ypos));
  1168. ypos += staffs.get(staffnum).getHeight();
  1169. }
  1170. }
  1171. /* Draw the page number */
  1172. canvas.drawText("" + pagenumber,
  1173. PageWidth-leftmargin,
  1174. topmargin + PageHeight - 12,
  1175. paint);
  1176. }
  1177. public void DrawMeasure(Canvas canvas, int measureIdx, int width_px) {
  1178. Vec2 xlim = new Vec2();
  1179. int leftmargin = 20;
  1180. int topmargin = 20;
  1181. int ypos = TitleHeight;
  1182. int ymin = TitleHeight;
  1183. int ymax = 0;
  1184. int curr_line_x = 0;
  1185. boolean is_clef = true;
  1186. for(Staff f : staffs) {
  1187. canvas.translate(leftmargin, t

Large files files are truncated, but you can click here to view the full file