Rechercher

PythonReactSEOPDF
GitHub Skill

alphatab-gp-files

Use when working with Guitar Pro file viewing, alphaTab integration, or the GP Library - covers service patterns, file handling, scale highlighting, and IndexedDB storage

EvanMcCormick☆ 0 Étoiles du dépôt0 Forks du dépôt

Installation

Suivez les instructions du dépôt pour votre agent. Consultez les fichiers et dépendances avant l’installation.

Voir la source ↗Guides

Instructions

alphaTab and Guitar Pro File Handling

Overview

The app uses alphaTab library to render and play Guitar Pro files (.gp, .gp3-.gp5, .gpx). This skill covers the service architecture, file handling patterns, and integration with scale/chord highlighting.

Core principle: alphaTab manages its own rendering and audio - wrap it in a service with BehaviorSubject state management.

When to Use

  • Modifying GP viewer functionality
  • Adding features to GP library
  • Integrating scale/chord highlighting with sheet music
  • Handling file upload/storage
  • Debugging alphaTab rendering issues

Architecture

Files Structure
components/
  gp-viewer/
    gp-viewer.component.ts         # Main viewer container
    gp-viewer.component.html
    gp-viewer.component.scss
    components/
      gp-scale-highlighter/        # Scale/chord highlight controls
  gp-library/
    gp-library.component.ts        # Library browser
    components/
      gp-file-card/                # File card display
      gp-library-filters/          # Search/filter controls

services/
  alpha-tab.service.ts             # alphaTab API wrapper
  gp-library.service.ts            # IndexedDB storage

models/
  alpha-tab.model.ts               # Interfaces
Service Layer
// alpha-tab.service.ts - Key patterns
@Injectable({ providedIn: 'root' })
export class AlphaTabService {
  private api: any = null;  // alphaTab.AlphaTabApi
  private stateSubject = new BehaviorSubject<AlphaTabState>(DEFAULT_STATE);

  constructor(private ngZone: NgZone) {}

  getState(): Observable<AlphaTabState> {
    return this.stateSubject.asObservable();
  }

  initializeApi(element: HTMLElement, settings: AlphaTabSettings): void {
    // Import alphaTab dynamically (it's large)
    import('@coderline/alphatab').then(alphaTab => {
      this.api = new alphaTab.AlphaTabApi(element, settings);

      // Wrap event handlers with NgZone for change detection
      this.api.scoreLoaded.on((score: any) => {
        this.ngZone.run(() => {
          this.updateState({ isLoaded: true, score });
        });
      });

      this.api.playerPositionChanged.on((args: any) => {
        this.ngZone.run(() => {
          this.updateState({
            timePosition: args.currentTime,
            tickPosition: args.currentTick
          });
        });
      });
    });
  }

  dispose(): void {
    this.api?.destroy();
    this.api = null;
    this.stateSubject.next(DEFAULT_STATE);
  }
}

Build Configuration

alphaTab requires custom webpack for WebWorkers and AudioWorklets, plus asset configuration for fonts and soundfonts.

Webpack Plugin
// custom-webpack.config.js
const { AlphaTabWebPackPlugin } = require('@coderline/alphatab-webpack');

module.exports = {
  plugins: [
    new AlphaTabWebPackPlugin()  // Copies fonts and soundfont during build
  ]
};
Angular.json Configuration

IMPORTANT: The webpack plugin only copies assets during full builds. For ng serve (dev server), you must also configure assets in angular.json:

// angular.json
{
  "architect": {
    "build": {
      "builder": "@angular-builders/custom-webpack:browser",
      "options": {
        "assets": [
          "src/favicon.ico",
          "src/assets",
          {
            "glob": "**/*",
            "input": "node_modules/@coderline/alphatab/dist/font",
            "output": "/font"
          },
          {
            "glob": "**/*",
            "input": "node_modules/@coderline/alphatab/dist/soundfont",
            "output": "/soundfont"
          }
        ],
        "customWebpackConfig": {
          "path": "./custom-webpack.config.js"
        }
      }
    },
    "serve": {
      "builder": "@angular-builders/custom-webpack:dev-server"
    }
  }
}
Asset Paths in Code

When initializing alphaTab, use these paths:

this.alphaTabService.initializeApi(element, {
  core: {
    fontDirectory: '/font/',  // Must match angular.json output
    useWorkers: true
  },
  player: {
    soundFont: '/soundfont/sonivox.sf2'  // Must match angular.json output
  }
});

File Handling

Loading Files
async loadFile(file: File): Promise<void> {
  this.updateState({ loadingState: 'loading' });

  try {
    const arrayBuffer = await file.arrayBuffer();
    this.api.load(arrayBuffer);
  } catch (error) {
    this.updateState({
      loadingState: 'error',
      errorMessage: 'Failed to load file'
    });
  }
}
Drag and Drop
@HostListener('dragover', ['$event'])
onDragOver(event: DragEvent): void {
  event.preventDefault();
  event.stopPropagation();
  this.isDragging = true;
}

@HostListener('drop', ['$event'])
onDrop(event: DragEvent): void {
  event.preventDefault();
  event.stopPropagation();
  this.isDragging = false;

  const files = event.dataTransfer?.files;
  if (files && files.length > 0) {
    const file = files[0];
    if (this.isValidGpFile(file)) {
      this.loadFile(file);
    }
  }
}

private isValidGpFile(file: File): boolean {
  const validExtensions = ['.gp', '.gp3', '.gp4', '.gp5', '.gpx'];
  return validExtensions.some(ext =>
    file.name.toLowerCase().endsWith(ext)
  );
}

GP Library (IndexedDB)

Storage Schema
interface GpLibraryEntry {
  id: string;              // Unique ID
  fileName: string;        // Original filename
  fileSize: number;        // Bytes
  fileData: ArrayBuffer;   // The actual file
  title: string;           // From score metadata
  artist?: string;
  album?: string;
  tempo?: number;
  trackCount?: number;
  key?: number;            // Detected key (0-11)
  detectedScales?: string[];
  detectedChords?: string[];
  dateAdded: Date;
}
Service Pattern
@Injectable({ providedIn: 'root' })
export class GpLibraryService {
  private db: IDBDatabase | null = null;
  private entriesSubject = new BehaviorSubject<GpLibraryEntry[]>([]);

  constructor(private ngZone: NgZone) {
    this.initDatabase();
  }

  async addFile(file: File, metadata: Partial<GpLibraryEntry>): Promise<GpLibraryEntry> {
    const reader = new FileReader();

    return new Promise((resolve, reject) => {
      reader.onload = () => {
        const entry: GpLibraryEntry = {
          id: this.generateId(),
          fileName: file.name,
          fileSize: file.size,
          fileData: reader.result as ArrayBuffer,
          title: metadata.title || file.name,
          dateAdded: new Date(),
          ...metadata
        };

        const transaction = this.db!.transaction('files', 'readwrite');
        const store = transaction.objectStore('files');
        const request = store.add(entry);

        request.onsuccess = () => {
          this.loadAllEntries();  // Refresh list
          resolve(entry);
        };
      };

      reader.readAsArrayBuffer(file);
    });
  }

  async getFileData(id: string): Promise<ArrayBuffer | null> {
    const entry = await this.getEntry(id);
    return entry?.fileData || null;
  }
}

Scale/Chord Highlighting

HighlightConfig Interface
interface HighlightConfig {
  enabled: boolean;
  rootNote: number;      // 0-11 (C=0)
  intervals: number[];   // Semitones from root
  name: string;          // Display name
  type: 'scale' | 'chord';
}
Applying Highlights
onHighlightChange(config: HighlightConfig): void {
  this.highlightConfig = config;
  this.applyHighlighting();
}

private applyHighlighting(): void {
  const container = this.alphaTabContainer.nativeElement;

  // Remove existing highlights
  container.querySelectorAll('.scale-highlight').forEach((el: Element) => {
    el.classList.remove('scale-highlight');
  });

  if (!this.highlightConfig?.enabled) return;

  // Calculate notes to highlight (0-11)
  const highlightedNotes = this.highlightConfig.intervals.map(interval =>
    (this.highlightConfig!.rootNote + interval) % 12
  );

  // Find note elements and apply class
  container.querySelectorAll('.at-note').forEach((noteEl: Element) => {
    const noteValue = noteEl.getAttribute('data-note');
    if (noteValue !== null) {
      const noteNum = parseInt(noteValue, 10) % 12;
      if (highlightedNotes.includes(noteNum)) {
        noteEl.classList.add('scale-highlight');
      }
    }
  });
}
Highlight Styles
// In component SCSS
.alphatab-container {
  ::ng-deep .at-note.scale-highlight {
    circle, ellipse {
      fill: rgba($accent-color, 0.3) !important;
      stroke: $accent-color !important;
      stroke-width: 2px !important;
    }
  }
}

Playback Controls

Basic Controls
play(): void {
  this.api?.play();
}

pause(): void {
  this.api?.pause();
}

stop(): void {
  this.api?.stop();
}

setVolume(value: number): void {
  if (this.api) {
    this.api.masterVolume = value;  // 0-1
  }
}

setPlaybackSpeed(multiplier: number): void {
  if (this.api) {
    this.api.playbackSpeed = multiplier;  // 0.25 to 2.0
  }
}
Track Controls
setTrackMute(trackIndex: number, muted: boolean): void {
  const track = this.api?.score?.tracks[trackIndex];
  if (track) {
    this.api.changeTrackMute([track], muted);
  }
}

setTrackSolo(trackIndex: number, solo: boolean): void {
  const track = this.api?.score?.tracks[trackIndex];
  if (track) {
    this.api.changeTrackSolo([track], solo);
  }
}

Common Issues

Issue Cause Solution
No audio User hasn't interacted alphaTab auto-handles, but may need click
Missing fonts Webpack plugin not configured Add AlphaTabWebPackPlugin
No soundfont Path incorrect Check /soundfont/sonivox.sf2 exists
Rendering blank Container too small Ensure container has dimensions
Memory leak Not disposing Call api.destroy() in ngOnDestroy
UI not updating Events outside Angular zone Use ChangeDetectorRef.detectChanges()
Assets missing in dev Only webpack plugin configured Add assets to angular.json

ChangeDetectorRef for UI Updates

alphaTab events fire outside Angular's zone. Even with NgZone.run() in the service, components may need manual change detection:

import { ChangeDetectorRef } from '@angular/core';

export class GpViewerComponent implements OnInit {
  constructor(
    private alphaTabService: AlphaTabService,
    private cdr: ChangeDetectorRef
  ) {}

  ngOnInit(): void {
    this.alphaTabService.getState()
      .pipe(takeUntil(this.destroy$))
      .subscribe(state => {
        this.state = state;
        // CRITICAL: Force change detection for alphaTab events
        this.cdr.detectChanges();
      });
  }
}

Checklist

When modifying GP viewer/library:

  • AlphaTabService wraps all API calls
  • NgZone.run() used for all event handlers in service
  • ChangeDetectorRef.detectChanges() used in component subscriptions
  • Files validated before loading
  • Dispose called in ngOnDestroy
  • IndexedDB transactions properly handled
  • Highlighting re-applied after score renders
  • Loading/error states handled in UI
  • File size considered for IndexedDB storage limits
  • Assets configured in angular.json for dev server
  • Custom webpack config includes AlphaTabWebPackPlugin

Exemples

Consultez les exemples du dépôt source.

Voir la source ↗

Historique source

Date d’indexation
2026-01-20
Mise à jour de l’index
2026-01-26
Branch
main

Les dates concernent ce répertoire. Consultez le dépôt pour l’historique du code.

GitHub ↗

chat Comments (0)

chat_bubble_outline

No comments yet. Be the first to share your thoughts!