Witam mam problem z którym nie mogę sobie poradzić. Próbowałem nwet z AI coś wykombinować ale to dopiero był dramat... :) , otóż uparłem się zrobić sekwencer midi (coś w rodzaju cakewalk 9) problem polega na tym, że nie mogę sobie poradzić z "przeliczeniem" odpowiedniego czasu przesuwu "czerwonej linii i odliczania taktów w czasie odtwarzania pliku midi. Normalnie mijają podczas odtwarzania załóżmy 4 takty, a u mnie przesunięcie zegara taktowego i tej linii odbywa się w "żółwim tempie" , gdzie cał utwór zmieści się w moich 2-może 3 taktach... jak sobie z tym poradzić, dodam, że 1 takt podzieliłem na 1920 "punktów, nutowych" gdzie można będzie zapisać dźwięki czyli przy metrum 4/4 każda ćwiartka to 480 takich punktów. może wrzucę mój unit "główny" to ktoś coś może doradzi.... może na coś wpadnie jak to zrobić... Dodam, że próbowałem użyć komponentu midi Simplified 1.6 ale coś nie idzie mi z nim i wróciłem do standartowego zegata TTimer z delphi. Z góry dzięki za pomoc,
unit SekwencerMidi;
interface
uses
Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls, Native.Devices.StdCtrls,
Native.Devices.Classes, Native.Midi.Classes, Native.Midi.Devices,
Vcl.ComCtrls, Vcl.ToolWin, Vcl.Menus, Vcl.ExtCtrls, Vcl.Grids,
Native.SysCtrls.Timers, Native.Midi.Sequencers,
Native.Midi.Files, Vcl.MPlayer, Vcl.ButtonGroup, Vcl.Buttons;
type
TForm1 = class(TForm)
MainMenu1: TMainMenu;
Plik1: TMenuItem;
Edycja1: TMenuItem;
EfektyMidi1: TMenuItem;
About1: TMenuItem;
Nowy1: TMenuItem;
Wczytaj1: TMenuItem;
Zapisz1: TMenuItem;
Zakocz1: TMenuItem;
StatusBar1: TStatusBar;
Panel1: TPanel;
Button1: TButton;
Button2: TButton;
Button3: TButton;
Button4: TButton;
Button5: TButton;
DrawGrid1: TDrawGrid;
Splitter1: TSplitter;
DrawGrid2: TDrawGrid;
MIDIOutDevices1: TMIDIOutDevices;
DeviceListBox1: TDeviceListBox;
SMFSequencer1: TSMFSequencer;
SMFFile1: TSMFFile;
OpenDialog1: TOpenDialog;
SaveDialog1: TSaveDialog;
Label2: TLabel;
Button6: TButton;
SpeedButton1: TSpeedButton;
SpeedButton2: TSpeedButton;
SpeedButton3: TSpeedButton;
SpeedButton4: TSpeedButton;
SpeedButton5: TSpeedButton;
Label3: TLabel;
Edit2: TEdit;
Label4: TLabel;
Edit3: TEdit;
Label1: TLabel;
Edit1: TEdit;
N3: TMenuItem;
N4: TMenuItem;
View1: TMenuItem;
View2: TMenuItem;
Nuty1: TMenuItem;
Edit4: TEdit;
Edit5: TEdit;
Label5: TLabel;
Label6: TLabel;
MediaPlayer1: TMediaPlayer;
Timer1: TTimer;
procedure FormCreate(Sender: TObject);
procedure DrawGrid1DrawCell(Sender: TObject; ACol, ARow: Integer; Rect: TRect; State: TGridDrawState);
procedure DrawGrid2DrawCell(Sender: TObject; ACol, ARow: Integer; Rect: TRect; State: TGridDrawState);
procedure SMFSequencer1Event(Sender: TObject; const ASequence, APosition: Cardinal);
procedure SysTimer1Timer(Sender: TObject);
procedure DrawGrid1SelectCell(Sender: TObject; ACol, ARow: Integer; var CanSelect: Boolean);
procedure DrawGrid2SelectCell(Sender: TObject; ACol, ARow: Integer; var CanSelect: Boolean);
procedure DrawGrid1TopLeftChanged(Sender: TObject);
procedure DrawGrid2TopLeftChanged(Sender: TObject);
procedure Edit2Change(Sender: TObject);
procedure Edit3Change(Sender: TObject);
procedure Wczytaj1Click(Sender: TObject);
procedure Button6Click(Sender: TObject);
procedure Button5Click(Sender: TObject);
procedure Zapisz1Click(Sender: TObject);
procedure Zakocz1Click(Sender: TObject);
procedure Markery1Click(Sender: TObject);
procedure Metrum1Click(Sender: TObject);
procedure Button1Click(Sender: TObject);
procedure Kwantyzacja1Click(Sender: TObject);
procedure ranspozycja1Click(Sender: TObject);
procedure About1Click(Sender: TObject);
procedure Button3Click(Sender: TObject);
procedure Button4Click(Sender: TObject);
procedure View1Click(Sender: TObject);
procedure N4Click(Sender: TObject);
procedure View2Click(Sender: TObject);
procedure Nuty1Click(Sender: TObject);
procedure Splitter1CanResize(Sender: TObject; var NewSize: Integer;
var Accept: Boolean);
procedure DrawGrid2DblClick(Sender: TObject);
procedure SpeedButton1Click(Sender: TObject);
procedure SpeedButton2Click(Sender: TObject);
procedure SpeedButton3Click(Sender: TObject);
procedure SpeedButton4Click(Sender: TObject);
procedure SpeedButton5Click(Sender: TObject);
procedure Timer1Timer(Sender: TObject);
private
FCurrentPos: Cardinal;
FIsSyncing: Boolean;
FGridData: array[0..8, 1..16] of string; // Od 1 do 16 dla tracków
FMaxPos: Cardinal; // Tu zapiszemy ostatni tick
public
FPPQ: Integer;
FNumerator: Integer;
FDenominator: Integer;
FTicksPerMeasure: Integer;
FTicksPerBeat: Integer;
FBeatsPerMeasure: Integer;
procedure UpdateGridWidth;
procedure RecalculateTimeStructure;
function GetMidiTimeStr(APosition: Cardinal): string;
end;
var
Form1: TForm1;
SequenceData: array[0..15, 0..500, 0..127] of Boolean;
implementation
{$R *.dfm}
uses Unit2, Unit3, Unit4, Unit5, Unit6, Unit7, Unit10, Unit8, Unit9;
procedure TForm1.SysTimer1Timer(Sender: TObject);
var
LBPM: Double;
TotalSec, H, M, S: Integer;
begin
if (MediaPlayer1.Mode = mpPlaying) or (MediaPlayer1.Mode = mpPaused) then
begin
LBPM := StrToFloatDef(Edit1.Text, 120.0);
if FPPQ > 0 then
FCurrentPos := Round((MediaPlayer1.Position * LBPM * FPPQ) / 60000);
// Czas zegarowy do Edit4 (0:00:00)
TotalSec := MediaPlayer1.Position div 1000;
H := TotalSec div 3600;
M := (TotalSec mod 3600) div 60;
S := TotalSec mod 60;
Edit4.Text := Format('%d:%.2d:%.2d', [H, M, S]);
// Pozycja taktowa do Edit5 (1.01.000)
Edit5.Text := StringReplace(GetMidiTimeStr(FCurrentPos), ':', '.', [rfReplaceAll]);
DrawGrid2.Invalidate;
end;
end;
procedure TForm1.Timer1Timer(Sender: TObject);
var
LBPM: Double;
TotalMS, H, M, S: Integer;
begin
if (MediaPlayer1.Mode <> mpStopped) then
begin
TotalMS := MediaPlayer1.Position; // Pobieramy MS
LBPM := StrToFloatDef(Edit1.Text, 120.0);
// KLUCZOWA POPRAWKA: Przeliczenie MS na Ticki MIDI
// Wzór: Ticks = (Milisekundy * BPM * PPQ) / 60000
if (FPPQ > 0) and (LBPM > 0) then
FCurrentPos := Round((Int64(TotalMS) * LBPM * FPPQ) / 60000);
// ZEGAR GODZINOWY (Edit4) - musi działać!
S := (TotalMS div 1000) mod 60;
M := (TotalMS div 60000) mod 60;
H := (TotalMS div 3600000);
Edit4.Text := Format('%d:%.2d:%.2d', [H, M, S]);
// ZEGAR TAKTOWY (Edit5)
Edit5.Text := StringReplace(GetMidiTimeStr(FCurrentPos), ':', '.', [rfReplaceAll]);
DrawGrid2.Invalidate; // Odświeżanie siatki
end;
end;
procedure TForm1.FormCreate(Sender: TObject);
begin
MediaPlayer1.TimeFormat := tfMilliseconds; // przeliczenie taktów
FIsSyncing := False;
FCurrentPos := 0;
FPPQ := 480;
// Wartości domyślne dla 2025
Edit2.Text := '4';
Edit3.Text := '4';
// --- PRZYWRÓCONA KONFIGURACJA DRAWGRID1 ---
DrawGrid1.ColCount := 7;
DrawGrid1.RowCount := 17;
DrawGrid1.FixedCols := 1;
DrawGrid1.FixedRows := 1;
DrawGrid1.Options := DrawGrid1.Options + [goRowSelect, goDrawFocusSelected];
DrawGrid1.ColWidths[0] := 35; // Track
DrawGrid1.ColWidths[1] := 25; // M
DrawGrid1.ColWidths[2] := 50; // Chanel
DrawGrid1.ColWidths[3] := 90; // Bank
DrawGrid1.ColWidths[4] := 90; // Patch
DrawGrid1.ColWidths[5] := 40; // Vol
DrawGrid1.ColWidths[6] := 40; // Pan
// --- KONFIGURACJA DRAWGRID2 ---
DrawGrid2.FixedRows := 1;
DrawGrid2.RowCount := 17;
DrawGrid2.ColCount := 400;
DrawGrid2.DefaultColWidth := 60; // Szerokość miary (ćwierćnuty/ósemki)
DrawGrid2.Options := DrawGrid2.Options + [goRowSelect, goDrawFocusSelected];
DrawGrid1.Align := alLeft;
Splitter1.Align := alLeft;
DrawGrid2.Align := alClient;
RecalculateTimeStructure;
UpdateGridWidth;
end;
procedure TForm1.RecalculateTimeStructure;
begin
// Pobieranie wartości z pól edycyjnych z zabezpieczeniem (default 4/4)
FNumerator := StrToIntDef(Edit2.Text, 4);
FDenominator := StrToIntDef(Edit3.Text, 4);
// Walidacja danych wejściowych
if FDenominator <= 0 then FDenominator := 4;
if FNumerator <= 0 then FNumerator := 1;
// LOGIKA OBSŁUGI METRUM
if (FDenominator = 8) and (FNumerator mod 3 = 0) and (FNumerator > 3) then
begin
FTicksPerBeat := Trunc(FPPQ * (4.0 / 8.0) * 3);
FBeatsPerMeasure := FNumerator div 3;
end
else
begin
FTicksPerBeat := Trunc(FPPQ * (4.0 / FDenominator));
FBeatsPerMeasure := FNumerator;
end;
FTicksPerMeasure := Trunc(FPPQ * (4.0 / FDenominator)) * FNumerator;
DrawGrid2.Invalidate;
end;
function TForm1.GetMidiTimeStr(APosition: Cardinal): string;
var Bar, Beat, Tick: Integer;
begin
if (FTicksPerMeasure <= 0) or (FTicksPerBeat <= 0) then Exit('1:01:000');
Bar := (Integer(APosition) div FTicksPerMeasure) + 1;
Beat := ((Integer(APosition) mod FTicksPerMeasure) div FTicksPerBeat) + 1;
Tick := (Integer(APosition) mod FTicksPerBeat);
Result := Format('%d:%.2d:%.3d', [Bar, Beat, Tick]);
end;
procedure TForm1.Kwantyzacja1Click(Sender: TObject);
begin
Form4.Show;
end;
procedure TForm1.Markery1Click(Sender: TObject);
begin
Form2.Show;
end;
procedure TForm1.Metrum1Click(Sender: TObject);
begin
Form3.Show;
end;
procedure TForm1.N4Click(Sender: TObject);
begin
Form7.Show;
end;
procedure TForm1.Nuty1Click(Sender: TObject);
begin
Form10.Show;
end;
procedure TForm1.ranspozycja1Click(Sender: TObject);
begin
Form5.Show;
end;
procedure TForm1.DrawGrid2DblClick(Sender: TObject);
begin
if DrawGrid2.Row > 0 then
begin
Form7.SelectedTrackID := DrawGrid2.Row;
Form7.Caption := Format('Piano Roll - Track %d [Metrum: %s/%s]',
[DrawGrid2.Row, Edit2.Text, Edit3.Text]);
Form7.Show;
end;
end;
procedure TForm1.DrawGrid2DrawCell(Sender: TObject; ACol, ARow: Integer; Rect: TRect; State: TGridDrawState);
var
CursorCol: Integer;
i: Integer;
PosX: Integer;
begin
// 1. TŁO
if gdSelected in State then
begin
DrawGrid2.Canvas.Brush.Color := clHighlight;
DrawGrid2.Canvas.Font.Color := clHighlightText;
end
else if ARow = 0 then
DrawGrid2.Canvas.Brush.Color := clBtnFace
else
DrawGrid2.Canvas.Brush.Color := clWhite;
DrawGrid2.Canvas.FillRect(Rect);
// 2. GRAFIKA WIDOKU OGÓLNEGO (Cakewalk Style)
if (ARow > 0) and (ARow <= 16) then
begin
// Skanujemy nuty w SequenceData dla tej komórki czasu
for i := 0 to 127 do
if (ACol < 501) and SequenceData[ARow-1, ACol, i] then
begin
DrawGrid2.Canvas.Brush.Color := clBlue;
DrawGrid2.Canvas.FillRect(Bounds(Rect.Left + 2, Rect.Top + 4, 4, Rect.Height - 8));
Break;
end;
end;
// 3. NAGŁÓWEK TAKTU
if (ARow = 0) and (FNumerator > 0) then
begin
if (ACol mod FNumerator) = 0 then
begin
DrawGrid2.Canvas.Font.Style := [fsBold];
DrawGrid2.Canvas.Font.Color := clBlack;
DrawGrid2.Canvas.TextOut(Rect.Left + 4, Rect.Top + 2, IntToStr((ACol div FNumerator) + 1));
end;
end;
// 4. RYSOWANIE LINII SIATKI
DrawGrid2.Canvas.Brush.Style := bsClear;
DrawGrid2.Canvas.Pen.Color := clSilver;
DrawGrid2.Canvas.Pen.Width := 1;
if (FNumerator > 0) and (ACol mod FNumerator = 0) then
begin
DrawGrid2.Canvas.Pen.Color := clGray;
DrawGrid2.Canvas.Pen.Width := 2;
DrawGrid2.Canvas.MoveTo(Rect.Left, Rect.Top);
DrawGrid2.Canvas.LineTo(Rect.Left, Rect.Bottom);
end;
DrawGrid2.Canvas.Pen.Width := 1;
DrawGrid2.Canvas.Pen.Color := clSilver;
DrawGrid2.Canvas.Rectangle(Rect);
// 5. RYSOWANIE CZERWONEJ LINII POSTĘPU
if FTicksPerBeat > 0 then
begin
if ACol = (Integer(FCurrentPos) div FTicksPerBeat) then
begin
DrawGrid2.Canvas.Pen.Color := clRed;
DrawGrid2.Canvas.Pen.Width := 2;
// Obliczanie płynnego przesunięcia wewnątrz komórki
PosX := Rect.Left + Round(((FCurrentPos mod Cardinal(FTicksPerBeat)) / FTicksPerBeat) * Rect.Width);
DrawGrid2.Canvas.MoveTo(PosX, Rect.Top);
DrawGrid2.Canvas.LineTo(PosX, Rect.Bottom);
end;
end;
end;
procedure TForm1.DrawGrid1DrawCell(Sender: TObject; ACol, ARow: Integer; Rect: TRect; State: TGridDrawState);
var
S: string;
begin
if gdSelected in State then
begin
DrawGrid1.Canvas.Brush.Color := clHighlight;
DrawGrid1.Canvas.Font.Color := clHighlightText;
end
else if (ARow = 0) or (ACol = 0) then
begin
DrawGrid1.Canvas.Brush.Color := clBtnFace;
DrawGrid1.Canvas.Font.Color := clWindowText;
end
else
begin
DrawGrid1.Canvas.Brush.Color := clWindow;
DrawGrid1.Canvas.Font.Color := clWindowText;
end;
DrawGrid1.Canvas.FillRect(Rect);
S := '';
if ARow = 0 then
begin
case ACol of
0: S := 'Track';
1: S := 'M';
2: S := 'Chan';
3: S := 'Bank';
4: S := 'Patch';
5: S := 'Vol';
6: S := 'Pan';
end;
end
else if ACol = 0 then
S := IntToStr(ARow);
if S <> '' then
begin
DrawGrid1.Canvas.Brush.Style := bsClear;
DrawText(DrawGrid1.Canvas.Handle, PChar(S), Length(S), Rect,
DT_CENTER or DT_VCENTER or DT_SINGLELINE or DT_NOPREFIX);
end;
DrawGrid1.Canvas.Pen.Color := clSilver;
DrawGrid1.Canvas.Brush.Style := bsClear;
DrawGrid1.Canvas.Rectangle(Rect);
end;
procedure TForm1.DrawGrid1SelectCell(Sender: TObject; ACol, ARow: Integer; var CanSelect: Boolean);
begin
if FIsSyncing then Exit;
FIsSyncing := True;
try DrawGrid2.Row := ARow; finally FIsSyncing := False; end;
end;
procedure TForm1.DrawGrid2SelectCell(Sender: TObject; ACol, ARow: Integer; var CanSelect: Boolean);
begin
if FIsSyncing then Exit;
FIsSyncing := True;
try DrawGrid1.Row := ARow; finally FIsSyncing := False; end;
end;
procedure TForm1.DrawGrid1TopLeftChanged(Sender: TObject);
begin
if FIsSyncing then Exit;
FIsSyncing := True;
try DrawGrid2.TopRow := DrawGrid1.TopRow; finally FIsSyncing := False; end;
end;
procedure TForm1.DrawGrid2TopLeftChanged(Sender: TObject);
begin
if FIsSyncing then Exit;
FIsSyncing := True;
try DrawGrid1.TopRow := DrawGrid2.TopRow; finally FIsSyncing := False; end;
end;
procedure TForm1.Edit2Change(Sender: TObject); begin RecalculateTimeStructure; end;
procedure TForm1.Edit3Change(Sender: TObject); begin RecalculateTimeStructure; end;
procedure TForm1.SMFSequencer1Event(Sender: TObject; const ASequence, APosition: Cardinal); begin FCurrentPos := APosition; end;
procedure TForm1.SpeedButton1Click(Sender: TObject);
begin
if (MediaPlayer1.FileName <> '') then
begin
MediaPlayer1.Position := 0;
FCurrentPos := 0;
Edit4.Text := '0:00:00';
Edit5.Text := '1.01.000';
DrawGrid2.Repaint;
end;
end;
procedure TForm1.SpeedButton2Click(Sender: TObject);
begin
if (MediaPlayer1.FileName <> '') then MediaPlayer1.Play;
end;
procedure TForm1.SpeedButton3Click(Sender: TObject);
begin
if (MediaPlayer1.FileName <> '') then MediaPlayer1.Pause;
end;
procedure TForm1.SpeedButton4Click(Sender: TObject);
begin
if (MediaPlayer1.FileName <> '') then MediaPlayer1.Pause;
end;
procedure TForm1.SpeedButton5Click(Sender: TObject);
var
BPM: Integer;
begin
if (MediaPlayer1.FileName <> '') then
begin
MediaPlayer1.Position := MediaPlayer1.Length;
BPM := StrToIntDef(Edit1.Text, 120);
FCurrentPos := Round((Int64(MediaPlayer1.Length) * BPM * FPPQ) / 60000);
Edit5.Text := StringReplace(GetMidiTimeStr(FCurrentPos), ':', '.', [rfReplaceAll]);
DrawGrid2.Repaint;
end;
end;
procedure TForm1.Splitter1CanResize(Sender: TObject; var NewSize: Integer;
var Accept: Boolean);
begin
NewSize := 257;
Accept := False;
end;
procedure TForm1.About1Click(Sender: TObject);
begin
Form6.Show;
end;
procedure TForm1.Button1Click(Sender: TObject);
var
FS: TFileStream;
MHeader: array[0..13] of Byte;
B: Byte;
TempoVal: Integer;
begin
if OpenDialog1.Execute then
begin
FS := TFileStream.Create(OpenDialog1.FileName, fmOpenRead or fmShareDenyNone);
try
FS.ReadBuffer(MHeader, 14);
// RĘCZNE WYDOBYCIE PPQ (Bajt 12 i 13)
FPPQ := (MHeader[12] shl 8) or MHeader[13];
// Skanowanie bajtów w poszukiwaniu Tempa (FF 51 03)
FS.Position := 14;
while FS.Position < FS.Size - 5 do
begin
FS.Read(B, 1);
if B = $FF then
begin
FS.Read(B, 1);
if B = $51 then // Tempo
begin
FS.Position := FS.Position + 1; // pomiń len
TempoVal := 0;
FS.Read(B, 1); TempoVal := (TempoVal shl 8) or B;
FS.Read(B, 1); TempoVal := (TempoVal shl 8) or B;
FS.Read(B, 1); TempoVal := (TempoVal shl 8) or B;
Edit1.Text := IntToStr(Round(60000000 / TempoVal));
Break;
end;
end;
end;
finally
FS.Free;
end;
if FPPQ <= 0 then FPPQ := 480;
MediaPlayer1.Close;
MediaPlayer1.FileName := OpenDialog1.FileName;
MediaPlayer1.Open;
RecalculateTimeStructure;
// Skalowanie ilości kolumn w siatce (jedna kolumna = jeden Beat)
FMaxPos := Round((Int64(MediaPlayer1.Length) * StrToFloatDef(Edit1.Text, 120) * FPPQ) / 60000);
if FTicksPerBeat > 0 then
DrawGrid2.ColCount := (Integer(FMaxPos) div FTicksPerBeat) + 20;
FCurrentPos := 0;
Timer1.Enabled := True;
DrawGrid2.Invalidate;
Form1.StatusBar1.Panels[0].Text := 'Wczytano: ' + opendialog1.filename;
end;
end;
procedure TForm1.Button3Click(Sender: TObject);
begin
Form4.Show;
end;
procedure TForm1.Button4Click(Sender: TObject);
begin
Form5.Show;
end;
procedure TForm1.Button5Click(Sender: TObject);
begin
form3.Show;
end;
procedure TForm1.Button6Click(Sender: TObject);
begin Form2.Show;
end;
procedure TForm1.UpdateGridWidth;
var I, TotalWidth: Integer;
begin
TotalWidth := 0;
for I := 0 to DrawGrid1.ColCount - 1 do TotalWidth := TotalWidth + DrawGrid1.ColWidths[I] + 1;
DrawGrid1.Width := TotalWidth + 4;
end;
procedure TForm1.View1Click(Sender: TObject);
begin
if DrawGrid2.Row > 0 then
begin
if not Assigned(Form8) then Form8 := TForm8.Create(Application);
Form8.LoadEventsFromTrack(DrawGrid2.Row);
Form8.Show;
end;
end;
procedure TForm1.View2Click(Sender: TObject);
begin
Form9.show;
end;
procedure TForm1.Wczytaj1Click(Sender: TObject);
begin
Button1Click(nil);
end;
procedure TForm1.Zakocz1Click(Sender: TObject);
begin
Form1.Close;
end;
procedure TForm1.Zapisz1Click(Sender: TObject);
begin
savedialog1.Execute;
end;
end.